Project NEXTSEQ_#154 (Liver development)

This notebook documents the steps for the aggregated data with no normalisation by Cellranger Researcher: Louisa Nelson PI: Stephen Taylor Analyst: Syed Murtuza Baker

Project Description

These samples are basically an addition to the one sample in the paper that was analysed previously. They are cells from patients with ovarian cancer, we isolated the tumour and stromal cells for each patient. And have mixed them back together to give 1 sample per patient.

So the only difference from the previous sample is that these have the tumour and stromals from the same patient mixed together in a 75%:25% ratio

Background

High-grade serous ovarian carcinoma is characterised by TP53 mutation and extensive chromosome instability (CIN). Because our understanding of CIN mechanisms is based largely on analysing established cell lines, we developed a workflow for generating ex vivo cultures from patient biopsies to provide models that support interrogation of CIN mechanisms in cells not extensively cultured in vitro. Here, we describe a “living biobank” of ovarian cancer models with extensive replicative capacity, derived from both ascites and solid biopsies.

Details of the project can be found in Nelson, L. et. al. A living biobank of ovarian cancer ex vivo models reveals profound mitotic heterogeneity. Nat Commun 11, 822 (2020)

Aim

The aims were

1. To cluster them establish the 2 populations
2. To establish the over-dispersed genes in the two populations

From the first sample p53 and XIST were differentially expressed so you could use these to validate the initial clustering. The analysis after that would be the same as the previous sample

Data preperation

The first thing is the load the libraries that we will be using in our downstream analysis

Loading library

library(Seurat)
library(scater)
library(ggplot2)
library(scran)
library(GGally)
library(mclust)
library(Rtsne)
library(viridis)
#library(umapr)
library(umap)
library(tidyverse)
library(paletteer)
library(biomaRt)
library(dynamicTreeCut)
library(ComplexHeatmap)
library(cluster)
library(factoextra)
library(edgeR)
writeLines(capture.output(sessionInfo()), "sessionInfo.txt")
sessionInfo()
R version 3.6.1 (2019-07-05)
Platform: x86_64-conda_cos6-linux-gnu (64-bit)
Running under: Ubuntu 16.04.6 LTS

Matrix products: default
BLAS/LAPACK: /home/mqbsxsm2/anaconda3/lib/R/lib/libRblas.so

locale:
[1] en_GB.UTF-8

attached base packages:
 [1] grid      parallel  stats4    stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] ComplexHeatmap_2.0.0        dynamicTreeCut_1.63-1       org.Hs.eg.db_3.8.2          AnnotationDbi_1.46.1        biomaRt_2.40.5             
 [6] paletteer_1.0.0             forcats_0.4.0               stringr_1.4.0               dplyr_0.8.3                 purrr_0.3.3                
[11] readr_1.3.1                 tidyr_1.0.0                 tibble_2.1.3                tidyverse_1.3.0             umap_0.2.4.1               
[16] viridis_0.5.1               viridisLite_0.3.0           Rtsne_0.15                  mclust_5.4.5                GGally_1.4.0               
[21] scran_1.12.1                scater_1.12.2               ggplot2_3.2.1               Seurat_3.1.2                SingleCellExperiment_1.6.0 
[26] SummarizedExperiment_1.14.1 DelayedArray_0.10.0         BiocParallel_1.18.1         matrixStats_0.55.0          Biobase_2.44.0             
[31] GenomicRanges_1.36.1        GenomeInfoDb_1.20.0         IRanges_2.18.3              S4Vectors_0.22.1            BiocGenerics_0.30.0        

loaded via a namespace (and not attached):
  [1] rappdirs_0.3.1           R.methodsS3_1.7.1        bit64_0.9-7              knitr_1.26               irlba_2.3.3             
  [6] multcomp_1.4-12          R.utils_2.9.2            data.table_1.12.8        RCurl_1.98-1.1           generics_0.0.2          
 [11] metap_1.2                cowplot_1.0.0            TH.data_1.0-10           RSQLite_2.2.0            RANN_2.6.1              
 [16] future_1.15.1            bit_1.1-15               mutoss_0.1-12            xml2_1.2.2               lubridate_1.7.4         
 [21] assertthat_0.2.1         xfun_0.12                hms_0.5.3                evaluate_0.14            fansi_0.4.1             
 [26] progress_1.2.2           caTools_1.17.1.4         dbplyr_1.4.2             readxl_1.3.1             igraph_1.2.4.2          
 [31] DBI_1.1.0                htmlwidgets_1.5.1        reshape_0.8.8            RSpectra_0.16-0          backports_1.1.5         
 [36] gbRd_0.4-11              RcppParallel_4.4.4       vctrs_0.2.1              ROCR_1.0-7               withr_2.1.2             
 [41] sctransform_0.2.1        prettyunits_1.1.0        mnormt_1.5-5             cluster_2.1.0            ape_5.3                 
 [46] lazyeval_0.2.2           crayon_1.3.4             edgeR_3.26.8             pkgconfig_2.0.3          labeling_0.3            
 [51] nlme_3.1-143             vipor_0.4.5              palr_0.1.0               pals_1.6                 rlang_0.4.2             
 [56] globals_0.12.5           lifecycle_0.1.0          sandwich_2.5-1           modelr_0.1.5             rsvd_1.0.2              
 [61] dichromat_2.0-0          cellranger_1.1.0         lmtest_0.9-37            Matrix_1.2-18            zoo_1.8-7               
 [66] reprex_0.3.0             base64enc_0.1-3          beeswarm_0.2.3           ggridges_0.5.2           GlobalOptions_0.1.1     
 [71] rjson_0.2.20             png_0.1-7                oompaBase_3.2.9          bitops_1.0-6             R.oo_1.23.0             
 [76] KernSmooth_2.23-16       blob_1.2.0               DelayedMatrixStats_1.6.1 shape_1.4.4              scales_1.1.0            
 [81] memoise_1.1.0            magrittr_1.5             plyr_1.8.5               ica_1.0-2                gplots_3.0.1.2          
 [86] bibtex_0.4.2.2           gdata_2.18.0             zlibbioc_1.30.0          compiler_3.6.1           lsei_1.2-0              
 [91] dqrng_0.2.1              RColorBrewer_1.1-2       clue_0.3-57              plotrix_3.7-7            fitdistrplus_1.0-14     
 [96] cli_2.0.1                XVector_0.24.0           listenv_0.8.0            pbapply_1.4-2            MASS_7.3-51.5           
[101] mgcv_1.8-31              tidyselect_0.2.5         stringi_1.4.5            yaml_2.2.0               BiocSingular_1.0.0      
[106] askpass_1.1              locfit_1.5-9.1           ggrepel_0.8.1            tools_3.6.1              future.apply_1.4.0      
[111] circlize_0.4.8           rstudioapi_0.10          gridExtra_2.3            farver_2.0.1             digest_0.6.23           
[116] Rcpp_1.0.3               broom_0.5.3              SDMTools_1.1-221.2       RcppAnnoy_0.0.14         httr_1.4.1              
[121] rsconnect_0.8.16         npsurv_0.4-0             Rdpack_0.11-1            colorspace_1.4-1         rvest_0.3.5             
[126] XML_3.99-0.3             fs_1.3.1                 reticulate_1.14          splines_3.6.1            uwot_0.1.5              
[131] statmod_1.4.33           rematch2_2.1.0           sn_1.5-4                 multtest_2.40.0          mapproj_1.2.6           
[136] plotly_4.9.1             jcolors_0.0.4            jsonlite_1.6             zeallot_0.1.0            scico_1.1.0             
[141] R6_2.4.1                 TFisher_0.2.0            pillar_1.4.3             htmltools_0.4.0          glue_1.3.1              
[146] BiocNeighbors_1.2.0      codetools_0.2-16         maps_3.3.0               tsne_0.1-3               mvtnorm_1.0-12          
[151] lattice_0.20-38          numDeriv_2016.8-1.1      curl_4.3                 ggbeeswarm_0.6.0         leiden_0.3.1            
[156] gtools_3.8.1             openssl_1.4.1            survival_3.1-8           limma_3.40.6             rmarkdown_2.0           
[161] munsell_0.5.0            GetoptLong_0.1.8         GenomeInfoDbData_1.2.1   haven_2.2.0              reshape2_1.4.3          
[166] gtable_0.3.0            

Setting up color palettes. The following code with produce a pie chart with colour configurations.

Setting up colorblind friendly color palette:

#cbPalette <- paletteer_d(package = "ggthemes", palette="calc", n=12)
 c30 <- c("dodgerblue2",#1
         "#E31A1C", #2 red
          "green4", #3
         "#FF7F00", #4 orange
           "green1",#5
         "purple",#6
         "blue1",#7
         "deeppink1",#8
         "darkorange4",#9
          "black",#10
         "gold1",#11
         "darkturquoise",#12
         "#6A3D9A", #13 purple
         "orchid1",#14
         "gray70",#15
          "maroon",#16
         "palegreen2",#17
          "#333333",#18
          "#CAB2D6", #19 lt purple
          "#FDBF6F", #20 lt orange
         "khaki2",#21
         "skyblue2",#22
         "steelblue4",#23
         "green1",#24
         "yellow4",#25
         "yellow3",#26
         "#FB9A99", #27 lt pink
         "brown",#28
         "#000099",#29
         "#CC3300"#30
         )
 
pie(rep(1,30), col=c30)

Choosing for the samples

c_sample_col <- c30[c(3,22,19,30)]

Choosing for the clusters

c_clust_col <- c30[c(1,2,3,4,5,6,7,8,11,14,22,24)]

__ This data set was not normalized using 10X downsampling as the samples have large variability in their cell numbers.__

Importing cell-ranger data

cellranger_pipestance_path <- "filtered_feature_bc_matrix_workshop/"
projectData<- Read10X(cellranger_pipestance_path)
#analysis_results <- load_cellranger_analysis_results(cellranger_pipestance_path)

Extracting cell information

## Sample information.  EDITS REQUIRED
## One sample
#Sample1_barcodes  <- data.frame(barcode=colnames(project_data), Sample="Eva_S1")
## Two (or more) samples
Sample1_barcodes  <- data.frame(barcode=colnames(projectData)[grep("1", colnames(projectData))], Sample="Patient1")
Sample2_barcodes  <- data.frame(barcode=colnames(projectData)[grep("2", colnames(projectData))], Sample="Patient2")
Sample3_barcodes  <- data.frame(barcode=colnames(projectData)[grep("3", colnames(projectData))], Sample="Patient3")
Sample4_barcodes  <- data.frame(barcode=colnames(projectData)[grep("4", colnames(projectData))], Sample="Patient4")
print(paste0('Number of Sample1 cells: ',dim(Sample1_barcodes)[1]))
[1] "Number of Sample1 cells: 588"
print(paste0('Number of Sample2 cells: ',dim(Sample2_barcodes)[1]))
[1] "Number of Sample2 cells: 369"
print(paste0('Number of Sample3 cells: ',dim(Sample3_barcodes)[1]))
[1] "Number of Sample3 cells: 593"
print(paste0('Number of Sample4 cells: ',dim(Sample4_barcodes)[1]))
[1] "Number of Sample4 cells: 450"

Confirmed this number with the QC output produced by cell ranger.

#Barcodes 
#For single sample
#annotBarcode <- Sample1_barcodes
#rownames(annotBarcode) <- annotBarcode$barcode
#head(annotBarcode)
## For multiple samples
annotBarcode <- rbind(Sample1_barcodes, Sample2_barcodes, Sample3_barcodes, Sample4_barcodes)
rownames(annotBarcode) <- annotBarcode$barcode
head(annotBarcode)

Creating SingleCellExperiment object

Creating the SingleCellExperiment object using scater. We would be using the exprs function of gbm. Later on scater might add its own function to input 10X data, but before that we will be using this method

cdSc <- SingleCellExperiment(assays = list(counts = as.matrix(projectData)))
colData(cdSc)$barcode <- annotBarcode$barcode
colData(cdSc)$Sample <- annotBarcode$Sample
rowData(cdSc)$symbol <- rownames(projectData)
cdSc <- scater::calculateQCMetrics(cdSc)
cdSc
class: SingleCellExperiment 
dim: 33538 2000 
metadata(0):
assays(1): counts
rownames(33538): MIR1302-2HG FAM138A ... AC213203.1 FAM231C
rowData names(8): symbol is_feature_control ... total_counts log10_total_counts
colnames(2000): CGAGTGCTCAGCTAGT-4 TTCTGTATCCACACAA-4 ... TTGCATTTCACCGGGT-1 TGGGCTGGTCAACCTA-3
colData names(11): barcode Sample ... pct_counts_in_top_200_features pct_counts_in_top_500_features
reducedDimNames(0):
spikeNames(0):

Normalization

Here we use log2-counts-per-million with an offset of 1 as the exprs values. We have to note that the CPM for scater is different than the regular CPM calculation as we are considering the step-size here.

The value of the log-CPMs is explained by adding a prior count to avoid undefined values after the log-transformation, multiplying by a million, and dividing by the mean library size. This size factors are used to define the effective library sizes. This is done by scaling all size factors such that the mean scaled size factor is equal to the mean sum of counts across all features. The effective library sizes are then used to in the denominator of the CPM calculation. The way that scater calculates the log-normalized counts are

lib.sizes <- colSums(counts(example_sceset))
lib.sizes <- lib.sizes/mean(lib.sizes)
log2(counts(example_sceset)[1,]/lib.sizes+1)

We now normalize for all the counts

exprs(cdSc) <- log2(scater::calculateCPM(cdSc, use_size_factors = TRUE) + 1)

Saving Dataset

save.image()
# save.image('PC_20181002.RData')

QC analysis

At the start of the QC analysis we make different plots to visualize the summary statistics.

Cell QC

Low-quality cells need to be identified and removed to ensure that the technical effects do not distort downstream analysis results. Three common measures of cell quality are:

  • The total counts or library size per cell
  • The number of expressed genes in each cell library
  • Proportion of mitochondrial genes in each cell library

The library size is defined as the total sum of counts across all genes. Cells with relatively small library sizes are considered to be of low quality as the RNA has not been efficiently captured (i.e., converted into cDNA and amplified) during library preparation.

The number of expressed genes in each cell is defined as the number of genes with non-zero counts for that cell. Any cell with very few expressed genes is likely to be of poor quality as the diverse transcript population has not been successfully captured.

High proportions of reads mapping to mitochondrial genes are indicative of poor-quality cells (Ilicic et al. 2016; Islam et al, 2014), possibly because of increased apoptosis and/or loss of cytoplasmic RNA from lysed cells.

Reads mapping with mitochondrial reads

We identify the rows corresponding to mitochondrial genes.

my.ids <- gsub('\\..*','',tolower(rownames(cdSc)))
ensembl = useEnsembl(biomart="ensembl", dataset="hsapiens_gene_ensembl", mirror = "useast")
#ensembl = useEnsembl(biomart="ensembl", dataset="hsapiens_gene_ensembl", host = "useast.ensembl.org")
chrName <- getBM(attributes=c('ensembl_gene_id','hgnc_symbol','chromosome_name'), filters = 'hgnc_symbol', values =my.ids, mart = ensembl)

Batch submitting query [==>----------------------------------------------------------------------------------------------------]   3% eta:  1m
Batch submitting query [====>--------------------------------------------------------------------------------------------------]   4% eta:  1m
Batch submitting query [=====>-------------------------------------------------------------------------------------------------]   6% eta:  1m
Batch submitting query [=======>-----------------------------------------------------------------------------------------------]   7% eta:  1m
Batch submitting query [========>----------------------------------------------------------------------------------------------]   9% eta:  1m
Batch submitting query [==========>--------------------------------------------------------------------------------------------]  10% eta: 47s
Batch submitting query [===========>-------------------------------------------------------------------------------------------]  12% eta: 45s
Batch submitting query [=============>-----------------------------------------------------------------------------------------]  13% eta: 43s
Batch submitting query [==============>----------------------------------------------------------------------------------------]  15% eta: 42s
Batch submitting query [================>--------------------------------------------------------------------------------------]  16% eta: 41s
Batch submitting query [=================>-------------------------------------------------------------------------------------]  18% eta: 39s
Batch submitting query [===================>-----------------------------------------------------------------------------------]  19% eta: 38s
Batch submitting query [====================>----------------------------------------------------------------------------------]  21% eta: 38s
Batch submitting query [======================>--------------------------------------------------------------------------------]  22% eta: 37s
Batch submitting query [=======================>-------------------------------------------------------------------------------]  24% eta: 36s
Batch submitting query [=========================>-----------------------------------------------------------------------------]  25% eta: 35s
Batch submitting query [==========================>----------------------------------------------------------------------------]  26% eta: 34s
Batch submitting query [============================>--------------------------------------------------------------------------]  28% eta: 33s
Batch submitting query [=============================>-------------------------------------------------------------------------]  29% eta: 32s
Batch submitting query [===============================>-----------------------------------------------------------------------]  31% eta: 31s
Batch submitting query [================================>----------------------------------------------------------------------]  32% eta: 30s
Batch submitting query [==================================>--------------------------------------------------------------------]  34% eta: 30s
Batch submitting query [===================================>-------------------------------------------------------------------]  35% eta: 29s
Batch submitting query [=====================================>-----------------------------------------------------------------]  37% eta: 28s
Batch submitting query [======================================>----------------------------------------------------------------]  38% eta: 28s
Batch submitting query [========================================>--------------------------------------------------------------]  40% eta: 27s
Batch submitting query [=========================================>-------------------------------------------------------------]  41% eta: 26s
Batch submitting query [===========================================>-----------------------------------------------------------]  43% eta: 26s
Batch submitting query [============================================>----------------------------------------------------------]  44% eta: 25s
Batch submitting query [==============================================>--------------------------------------------------------]  46% eta: 24s
Batch submitting query [===============================================>-------------------------------------------------------]  47% eta: 23s
Batch submitting query [=================================================>-----------------------------------------------------]  49% eta: 23s
Batch submitting query [===================================================>---------------------------------------------------]  50% eta: 22s
Batch submitting query [====================================================>--------------------------------------------------]  51% eta: 21s
Batch submitting query [======================================================>------------------------------------------------]  53% eta: 21s
Batch submitting query [=======================================================>-----------------------------------------------]  54% eta: 20s
Batch submitting query [=========================================================>---------------------------------------------]  56% eta: 19s
Batch submitting query [==========================================================>--------------------------------------------]  57% eta: 19s
Batch submitting query [============================================================>------------------------------------------]  59% eta: 18s
Batch submitting query [=============================================================>-----------------------------------------]  60% eta: 17s
Batch submitting query [===============================================================>---------------------------------------]  62% eta: 17s
Batch submitting query [================================================================>--------------------------------------]  63% eta: 16s
Batch submitting query [==================================================================>------------------------------------]  65% eta: 15s
Batch submitting query [===================================================================>-----------------------------------]  66% eta: 15s
Batch submitting query [=====================================================================>---------------------------------]  68% eta: 14s
Batch submitting query [======================================================================>--------------------------------]  69% eta: 13s
Batch submitting query [========================================================================>------------------------------]  71% eta: 13s
Batch submitting query [=========================================================================>-----------------------------]  72% eta: 12s
Batch submitting query [===========================================================================>---------------------------]  74% eta: 11s
Batch submitting query [============================================================================>--------------------------]  75% eta: 11s
Batch submitting query [==============================================================================>------------------------]  76% eta: 10s
Batch submitting query [===============================================================================>-----------------------]  78% eta:  9s
Batch submitting query [=================================================================================>---------------------]  79% eta:  9s
Batch submitting query [==================================================================================>--------------------]  81% eta:  8s
Batch submitting query [====================================================================================>------------------]  82% eta:  7s
Batch submitting query [=====================================================================================>-----------------]  84% eta:  7s
Batch submitting query [=======================================================================================>---------------]  85% eta:  6s
Batch submitting query [========================================================================================>--------------]  87% eta:  6s
Batch submitting query [==========================================================================================>------------]  88% eta:  5s
Batch submitting query [===========================================================================================>-----------]  90% eta:  4s
Batch submitting query [=============================================================================================>---------]  91% eta:  4s
Batch submitting query [==============================================================================================>--------]  93% eta:  3s
Batch submitting query [================================================================================================>------]  94% eta:  2s
Batch submitting query [=================================================================================================>-----]  96% eta:  2s
Batch submitting query [===================================================================================================>---]  97% eta:  1s
Batch submitting query [====================================================================================================>--]  99% eta:  1s
Batch submitting query [=======================================================================================================] 100% eta:  0s
                                                                                                                                              
is.mito <- chrName$chromosome_name == "MT" & !is.na(chrName$chromosome_name)
sum(is.mito)
[1] 13
## Mouse
#is.mito <- grepl("^mt-", rowData(cdSc)$symbol)
#cdSc <- calculateQCMetrics(cdSc, feature_controls=list(Mt=is.mito))
#print(paste0('Number of annotated mitochondrial genes = ',as.character(sum(is.mito))))
cdSc <- calculateQCMetrics(cdSc, feature_controls=list(Mt=is.mito))

Plotting the histograms of my QC entries.

par(mfrow=c(2,2))
hist((cdSc$total_counts)/1e6, xlab="Library sizes (millions)", main="Histogram of Library size",
     breaks=100, col="grey80", ylab="Number of cells")
hist(cdSc$total_features_by_counts, xlab="Number of expressed genes", main="Histogram of No. of Features",
     breaks=100, col="grey80", ylab="Number of cells")
hist(cdSc$pct_counts_Mt, xlab="Mitochondrial proportion (%)",
     ylab="Number of cells", breaks=100, main="Histogram of Mictochondria %", col="grey80")

The summary statistics for mitochondrial read proportion is reasonably good. As anything below 10% is very good and here the mean is around 7%. However, there are some outlier cells expressing very high mitocondrial genes. I am going to filter those genes out.

summary(cdSc$pct_counts_Mt)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
0.00000 0.01286 0.03079 0.03909 0.05497 0.44395 

__ MITOCHONDRIAL GENES QC NOTES __

  • A mean below 10% is very good.
  • A mean above 25% is no good.

MITOCHONDRIAL GENES QC NOTES FOR THIS DATASET

  • The summary statistics for mitochondrial read proportion is very good.
  • However, there are some outlier cells expressing very high mitocondrial genes. These will be filtered out.

It is also valuable to examine how the QC metrics behave with respect to each other. Generally, they will be in rough agreement, i.e., cells with low total counts will also have low numbers of expressed features and high mitochondrial proportions.

par(mfrow=c(1,3))
plot(cdSc$total_features_by_counts, cdSc$total_counts/1e6, xlab="Number of expressed genes",
    ylab="Library size (millions)")
plot(cdSc$total_features_by_counts, cdSc$pct_counts_Mt, xlab="Number of expressed genes",
    ylab="Mitochondrial proportion (%)")
plot(cdSc$total_counts/1e6, cdSc$pct_counts_Mt, xlab="Total counts (in millions)",
    ylab="Mitochondrial proportion (%)")

plotRowData(cdSc, x = "n_cells_by_counts", y = "log10_total_counts", colour_by = "is_feature_control_Mt")

This plot shows log10 total counts per gene (y-axis) versus number of cells expressing that gene (x-axis). Generally, in single cell datasets there are some genes with very low, or very high, total counts, which accounts for the S shape of the plot.

In the figure above we see that some of the mitochondrial genes (marked in orange) at the top right corner of the figure has very high log10_total_counts and also are expressed in a large number of cells (almost in all cells). This is not very surprising as some of the mitochondrial genes are indeed expressed across all the cells as they are producing energy. The worrying part would be if these genes takes majority of a cell’s read (more than 80%). Then I would remove those cells.

plotRowData(cdSc, x = "n_cells_by_counts", y = "log10_total_counts", colour_by = "pct_dropout_by_counts")

Dropouts is a term for genes with no read counts. As expected, cells with low number of reads have higher dropout counts.

plotColData(cdSc, x = "log10_total_counts", y = "log10_total_features_by_counts", colour_by = "pct_counts_Mt")

Cells on the linear pattern show a good correlation between total counts and the number of gene features with counts. Problematic cells deviate from the linear trend.

Again cells with lower read counts and lower total feature counts have very high proportion of mitchondrial reads, clearly indicating that these cells either started apoptosis or broken abruptly. We can safely remove these cells in our QC filtering.

plotColData(cdSc, x = "log10_total_features_by_counts", y="log10_total_counts", size_by ="total_counts_Mt", colour_by  = "pct_counts_Mt")

The following figure gives an idea of how many genes are expressed in how many cells.

plotExprsFreqVsMean(cdSc)

3. Quality filtering of cells

Picking thresholds for filtering out poor cells is not straightforward for different metrics as their absolute values depend on the protocol and biological system. For example, sequencing to greater depth will lead to more reads, regardless of the quality of the cells. To obtain an adaptive threshold, the assumption made here is that most of the dataset consists of high-quality cells. Plots to facilitate picking thresholds for cell cutoffs are below.

cut_off_reads <- median(cdSc$log10_total_counts) - 3*mad(cdSc$log10_total_counts)
df <- data.frame(x=cdSc$log10_total_counts, Sample = cdSc$Sample)
plot_reads <- ggplot(df,
       aes(x = x, fill = as.factor(Sample))) + 
       geom_density(alpha = 0.5) +
       geom_vline(xintercept = cut_off_reads, colour="grey", linetype = "longdash") +
       labs(x = expression('log'[10]*'(Library Size)'), title = "Total reads density", fill = "Sample") + 
       theme_classic(base_size = 14)+
       scale_fill_manual(values=c_sample_col)
cut_off_mRNA <- median(cdSc$log10_total_features_by_counts) - 3*mad(cdSc$log10_total_features_by_counts)
df <- data.frame(x=cdSc$log10_total_features_by_counts, Sample = cdSc$Sample)
plot_mRNA <- ggplot(df,
       aes(x = x, fill = as.factor(Sample))) + 
       geom_density(alpha = 0.5) +
       geom_vline(xintercept = cut_off_mRNA, colour="grey", linetype = "longdash") +
       labs(x = expression('log'[10]*'(Number of expressed genes)'), title = "Total genes expressed", fill = "Sample") + 
       theme_classic(base_size = 14)+
       scale_fill_manual(values=c_sample_col)
cut_off_MT <- median(cdSc$pct_counts_Mt) + 3*mad(cdSc$pct_counts_Mt)
df <- data.frame(x=cdSc$pct_counts_Mt, Sample = cdSc$Sample)
plot_MT <- ggplot(df,
       aes(x = x, fill = as.factor(Sample))) + 
       geom_density(alpha = 0.5) +
       geom_vline(xintercept = cut_off_MT, colour="grey", linetype = "longdash") +
       labs(x = expression('Pct of MT Expression'), title = "Mitochondrial Expression", fill = "Sample") + 
       theme_classic(base_size = 14)+
       scale_fill_manual(values=c_sample_col)
multiplot(plot_reads, plot_MT, plot_mRNA,  cols=2)

df <- data.frame(x=cdSc$log10_total_counts)
as_tibble(df) %>%
  dplyr::mutate("Sample" = cdSc$Sample) %>%
  ggplot( aes(x = x, fill = as.factor(Sample))) + 
       geom_density(alpha = 0.5) +
       geom_vline(xintercept = cut_off_reads, colour="grey", linetype = "longdash") +
       labs(x = expression('log'[10]*'(Library Size)'), title = "Total reads density", fill = "Sample") + 
       theme_classic(base_size = 14)+
       scale_fill_manual(values=c_sample_col) + facet_wrap(~Sample, nrow=2,ncol=2)

The distribution of the reads, number of expressed genes and the percent of MT expression.

The dotted line represents the threshold which is the 3 Median Absolute Deviation (MAD). For the top two figures, cells on the left of this threshold would be filtered out and for the bottom figure, cells on the right of this figure would be filtered out.

Cells are removed with log-library sizes that are more than 3 median absolute deviations (MADs) below the median log-library size. (A log-transformation improves resolution at small values, especially when the MAD of the raw values is comparable to or greater than the median).

Similarly cells are removed where the log-transformed number of expressed genes is 3 MADs below the median.

Finally, cells are removed having pct of MT expression below 3 MAD.

print(paste0('Read count Cutoff: ',10^cut_off_reads))
[1] "Read count Cutoff: 1325.66714005457"
print(paste0('Genes count Cutoff: ',10^cut_off_mRNA))
[1] "Genes count Cutoff: 884.168125530497"
print(paste0('MT percent count Cutoff: ',cut_off_MT))
[1] "MT percent count Cutoff: 0.120234511745261"

We now calculate the cells and genes that are deemed to be outliers due to falling below 3 MAD.

Cells having read counts below 1325 (ByLibSize) and number of genes expressed below 884 (ByFeature) would be dropped and if cells are having more than 0.12% of mitochondrial reads, I would drop the cells.

libsize.drop <- isOutlier(cdSc$total_counts, nmads=3, type="lower", log=TRUE)
feature.drop <- isOutlier(cdSc$total_features_by_counts, nmads=3, type="lower", log=TRUE)
mito.drop <- isOutlier(cdSc$pct_counts_Mt, nmads=3, type="higher")

Below are the stats for the cells that would be dropped

data.frame(ByLibSize=sum(libsize.drop), ByFeature=sum(feature.drop), 
           ByMito=sum(mito.drop))

So, based on library size, 825 cells would be dropped and based on number of features expressed, 1210 cells would be dropped and for Mitochondrial proportion of reads 517 cells would be dropped. Please note that all these cells are removed from the total population and also many of these cells would be overlapping across conditions.

Generating, the scater object with filtered profile.

cdScFilt <- cdSc[,!(libsize.drop | feature.drop | mito.drop)]
cdScFilt <- calculateQCMetrics(cdScFilt)

Before filtering number of cells were,

print(paste0("Cells before filtering: ",dim(cdSc)[2]))
[1] "Cells before filtering: 2000"

After filtering remaining cells are:

print(paste0("Cells remaining after filtering: ",dim(cdScFilt)[2]))
[1] "Cells remaining after filtering: 1828"

For the four samples, number of cells that are remaining are:

print(levels(as.factor(colData(cdSc)$Sample)))
[1] "Patient1" "Patient2" "Patient3" "Patient4"
print(paste0('Before Filtering: ', table(colData(cdSc)$Sample)))
[1] "Before Filtering: 588" "Before Filtering: 369" "Before Filtering: 593" "Before Filtering: 450"
print(paste0('After Filtering: ', table(colData(cdScFilt)$Sample)))
[1] "After Filtering: 548" "After Filtering: 352" "After Filtering: 516" "After Filtering: 412"

So, the cells that are being filtered out are kind of evenly distributed across the samples. This is kind of reassuring that there was not a point failure where one sample totally failed. After filtering we have in total 1828 cells remaining for downstream analysis which is a very reasonable number.

QC plots below to check result and decide whether further filtering is required.

Relationship between total counts and total features before filtering:

p <- plotColData(cdSc, x="log10_total_counts",y="total_features_by_counts", colour_by = "Sample") +
       scale_fill_manual(values=c_sample_col)
Scale for 'fill' is already present. Adding another scale for 'fill', which will replace the existing scale.
p

After filtering:

p <- p <- plotColData(cdScFilt, x="log10_total_counts",y="total_features_by_counts", colour_by = "Sample") +
       scale_fill_manual(values=c_sample_col)
Scale for 'fill' is already present. Adding another scale for 'fill', which will replace the existing scale.
p

We could see that some of the outlier cells have been dropped.

Before filtering:

plotColData(cdSc, x = "log10_total_counts", y = "log10_total_features_by_counts", colour_by = "pct_counts_Mt")

After filtering:

plotColData(cdScFilt, x = "log10_total_counts", y = "log10_total_features_by_counts", colour_by = "pct_counts_Mt")

We see that the good cells are kept while cells with very high Mt reads are being removed.

Violin plots below to investigate possible outlier cells with high read-depth (could be doublets).

df <- data.frame(Cell=colnames(cdScFilt), CellType=cdScFilt$Sample, totalFeatures=cdScFilt$total_features_by_counts, totalCount=cdScFilt$total_counts, PctTotalCountMt=cdScFilt$pct_counts_Mt, Sample=cdScFilt$Sample)
p1 <- ggplot(df, aes(factor(CellType),totalCount,colour=Sample))
p1 <- p1 + geom_violin() + geom_jitter(height = 0, width = 0.1) + theme_classic(base_size = 14) + scale_colour_manual(values=c_sample_col) +
  theme(axis.text.x=element_text(angle=90, hjust=1))
p2 <- ggplot(df, aes(factor(CellType),PctTotalCountMt,colour=Sample))
p2 <- p2 + geom_violin() + geom_jitter(height = 0, width = 0.1) + theme_classic(base_size = 14) + scale_colour_manual(values=c_sample_col) +
  theme(axis.text.x=element_text(angle=90, hjust=1))
p3 <- ggplot(df, aes(factor(CellType),totalFeatures,colour=Sample))
p3 <- p3 + geom_violin() + geom_jitter(height = 0, width = 0.1) + theme_classic(base_size = 14) + scale_colour_manual(values=c_sample_col) +
  theme(axis.text.x=element_text(angle=90, hjust=1))
multiplot(p1,p2,p3,cols = 2)

It clearly looks like that two cells above total counts of 30000 are outliers. This again would be removed as it would otherwise might be multiplets

cdScFilt
class: SingleCellExperiment 
dim: 33538 1828 
metadata(0):
assays(2): counts logcounts
rownames(33538): MIR1302-2HG FAM138A ... AC213203.1 FAM231C
rowData names(9): symbol is_feature_control_Mt ... total_counts log10_total_counts
colnames(1828): CGAGTGCTCAGCTAGT-4 TTCTGTATCCACACAA-4 ... TTGCATTTCACCGGGT-1 TGGGCTGGTCAACCTA-3
colData names(38): barcode Sample ... pct_counts_in_top_200_features pct_counts_in_top_500_features
reducedDimNames(0):
spikeNames(0):

Manual cutoff selection required in the next step. Careful this changes cdsFilt permanently. make a temp file if unsure

cdScFilt <- cdScFilt[,!(cdScFilt$total_counts > 20000)]
cdScFilt
class: SingleCellExperiment 
dim: 33538 1825 
metadata(0):
assays(2): counts logcounts
rownames(33538): MIR1302-2HG FAM138A ... AC213203.1 FAM231C
rowData names(9): symbol is_feature_control_Mt ... total_counts log10_total_counts
colnames(1825): CGAGTGCTCAGCTAGT-4 TTCTGTATCCACACAA-4 ... TTGCATTTCACCGGGT-1 TGGGCTGGTCAACCTA-3
colData names(38): barcode Sample ... pct_counts_in_top_200_features pct_counts_in_top_500_features
reducedDimNames(0):
spikeNames(0):

Drawing the vuiolin plot again

df <- data.frame(Cell=colnames(cdScFilt), CellType=cdScFilt$Sample, totalFeatures=cdScFilt$total_features_by_counts, totalCount=cdScFilt$total_counts, PctTotalCountMt=cdScFilt$pct_counts_Mt, Sample=cdScFilt$Sample)
p1 <- ggplot(df, aes(factor(CellType),totalCount,colour=Sample))
p1 <- p1 + geom_violin() + geom_jitter(height = 0, width = 0.1) + theme_classic(base_size = 14) + scale_colour_manual(values=c_sample_col) +
  theme(axis.text.x=element_text(angle=90, hjust=1))
p2 <- ggplot(df, aes(factor(CellType),PctTotalCountMt,colour=Sample))
p2 <- p2 + geom_violin() + geom_jitter(height = 0, width = 0.1) + theme_classic(base_size = 14) + scale_colour_manual(values=c_sample_col) +
  theme(axis.text.x=element_text(angle=90, hjust=1))
p3 <- ggplot(df, aes(factor(CellType),totalFeatures,colour=Sample))
p3 <- p3 + geom_violin() + geom_jitter(height = 0, width = 0.1) + theme_classic(base_size = 14) + scale_colour_manual(values=c_sample_col) +
  theme(axis.text.x=element_text(angle=90, hjust=1))
multiplot(p1,p2,p3,cols = 2)

Looks a good distribution to move forward.

save.image()

4. Classification of cell cycle phase

The prediction method used here is described by Scialdone et al. (2015) to classify cells into cell cycle phases based on the gene expression data. Using a training dataset, the sign of the difference in expression between two genes was computed for each pair of genes. Pairs with changes in the sign across cell cycle phases were chosen as markers. Cells in a test dataset can then be classified into the appropriate phase, based on whether the observed sign for each marker pair is consistent with one phase or another. We do the cell cycle classification before gene filtering as this provides more precise cell cycle phase classifications. This approach is implemented in the Cyclone function using a pre-trained set of marker pairs for human data. Some additional work is necessary to match the gene symbols in the data to the Ensembl annotation in the pre-trained marker set.

cdScFiltAnnot <- cdScFilt
hg.pairs <- readRDS(system.file("exdata", "human_cycle_markers.rds", package="scran"))
library(org.Hs.eg.db)
Loading required package: AnnotationDbi

Attaching package: ‘AnnotationDbi’

The following object is masked from ‘package:dplyr’:

    select
anno <- select(org.Hs.eg.db, keys=as.character(rownames(cdScFiltAnnot)), keytype="SYMBOL", column="ENSEMBL")
'select()' returned 1:many mapping between keys and columns
ensembl <- anno$ENSEMBL[match(as.character(rownames(cdScFiltAnnot)), anno$SYMBOL)]
assignments <- cyclone(cdScFiltAnnot, hg.pairs, gene.names=ensembl)
df <- data.frame(x=assignments$score$G1, y=assignments$score$G2M, Sample=colData(cdScFilt)$Sample)
 p<-ggplot(data=df, aes(x=x,y=y,color=Sample)) + 
    geom_point(size=0.5)+
    xlab("G1 score")+
    ylab("G2M score")+
    ylim(0,1)+
    xlim(0,1)+
    ggtitle("Cell-cycle effects")+
    theme_light(base_size=15)+
    theme(axis.title.x = element_text(size=10, vjust=-2),
          axis.text.x  = element_text( size=10),
          axis.title.y = element_text( size=10,vjust=2),
          axis.text.y  = element_text( size=10)) +
    theme(plot.margin=unit(c(1,1,1.5,1.2),"cm"))+
    theme(legend.text=element_text(size=10),#size of legend
          legend.title=element_text(size=10), 
          plot.title = element_text(size=20, face="bold")) +
    scale_colour_manual(values=c_sample_col) + 
    #scale_color_discrete(name=legend.title)+
    geom_segment(aes(x = 1/2, y = 0, xend=1/2, yend=1/2),colour="black") + 
    geom_segment(aes(x = 0, y = 1/2, xend=1/2, yend=1/2),colour="black") +
    geom_segment(aes(x = 1/2, y = 1/2, xend=1, yend=1),colour="black") +
    annotate("text", x=0.05, y=0.05, label="S", size=8)+
    annotate("text", x=0.95, y=0.25, label="G1", size=8)+
    annotate("text", x=0.25, y=0.95, label="G2M", size=8)
print(p)

smoothScatter(assignments$score$G1, assignments$score$G2M, xlab="G1 score", ylab="G2/M score", pch=16, cex=0.6)

Background Cell Cycle Analysis - Cells are classified as being in G1 phase (not in cell division aka cell cycle) if their G1 score is above 0.5 and greater than the G2/M score. - Cells are classified as being in S phase (synthesis of DNA, replication) if neither score is above 0.5. - Cells are classified as being in G2/M phase (gap between DNA synthesis and mitosis) if their G2/M score is above 0.5 and greater than the G1 score.

The cell-cycle status of cells can be a significant confounding factor in some datasets i.e. clusters forming on the basis of cell cycle status instead of other biological factors of interest. The goal at this stage is only to assess the cell cycle status of cells not to try normalise it away.

This method would be less accurate for data that are substantially different from those used in the training set, e.g., due to the use of a different protocol. This dataset uses UMI counts, which has an entirely different set of biases, e.g., 3’-end coverage only, no length bias, no amplification noise. These new biases (and the absence of expected biases) may interfere with accurate classification of some cells. So there is some uncertainty with this analysis.

Nevertheless we need to keep in mind that there could be quite high cell-cycle effect which might confound the dataset. To avoid problems from misclassification, no processing of this dataset by cell cycle phase will be done here.

This Dataset’s Cell Cycle result It looks like majority of the cells have a high G2/M or S score indicating that the cells are somewhat going through cell-cycle stages. Generally if they are on G1 stage, then they are not in cell-cycle stage and if on G2/M then they are cell-cycle stages. But here they are in S, which is the synthesis phase indicating DNA is being replicated.

colData(cdScFilt)$CellCycle <- assignments$phases
colData(cdScFiltAnnot)$CellCycle <- assignments$phases
plotPCA(cdScFiltAnnot, colour_by = "CellCycle", shape_by="Sample")

It does not look like one patient is dominated by a cell-cycle phase.

5. Filtering out low-abundance genes

The goal is to keep genes that will be good features to discriminate the cells.

Low-abundance genes are problematic as zero or near-zero counts do not contain enough information for reliable statistical inference (Bourgon et al., 2010). In addition, the discreteness of the counts may interfere with downstream statistical procedures, e.g., by compromising the accuracy of continuous approximations.

The more low frequency, low expression genes the more noise.

These genes are likely to be dominated by drop-out events (Brennecke et al., 2013), which limits their usefulness in later analyses. Removal of these genes improves discreteness and reduces the amount of computational work without major loss of information.

Generally in scRNA-seq low-abundance genes are defined as those with an average count below a filter threshold of 1. But 10X Chromium is based on UMI counts, which are lower but better quality counts, so setting the threshold to 1 would filter a large number of cells unfairly.

In the analysis below we go through an iterative process using the figure below, starting with 0.01 to assess what cutoff to use. To check whether the chosen threshold is suitable, we examine the distribution of log-means across all genes (Figure below). Generally for higher number of cells there is a peak on the right hand side that represents the bulk of moderately expressed genes while in the middle there is a rectangular component that corresponds to lowly expressed genes. The filter threshold should cut the distribution at some point along the rectangular component to remove the majority of low-abundance genes. As the blue line repsresents in the figure below, it cuts the counts at the rectangular component. Generally 9,000 - 14,000 genes is good (mouse) and 7,500-14,000 for human.

ave.counts <- rowMeans(counts(cdScFilt))
keepA <- ave.counts >= 0.01
keepB <- ave.counts >= 0.02
print(paste0('Log10 av count >=0.01 leaves ',as.character(sum(keepA)), ' genes '))
[1] "Log10 av count >=0.01 leaves 13194 genes "
print(paste0('Log10 av count >=0.02 leaves ',as.character(sum(keepB)), ' genes '))
[1] "Log10 av count >=0.02 leaves 11892 genes "
# plot of both cut offs
hist(log10(ave.counts), breaks=100, main="", col="grey80",
     xlab=expression(Log[10]~"average count"))
abline(v=log10(0.02), col="blue", lwd=2, lty=2)
text(log10(0.035),1000, "0.02")
abline(v=log10(0.01), col="blue", lwd=2, lty=2)
text(log10(0.006), 1000, "0.01")

After filtering with average count of 0.01 there are 13194 genes left for the downsteram analysis.

To check whether the chosen threshold is suitable, we examine the distribution of log-means across all genes (Figure below). Generally for higher number of cells there is a peak on the right hand side that represents the bulk of moderately expressed genes while in the middle there is a rectangular component that corresponds to lowly expressed genes. The filter threshold should cut the distribution at some point along the rectangular component to remove the majority of low-abundance genes. As the blue line repsresents in the figure below, it cuts the counts at the rectangular component.

We will look at the identities of the most highly expressed genes before filtering them. This should generally be dominated by constitutively expressed transcripts, such as those for ribosomal or mitochondrial proteins. The presence of other classes of features may be cause for concern if they are not consistent with expected biology. For example, the absence of ribosomal proteins and/or the presence of their pseudogenes are indicative of suboptimal alignment.

plotHighestExprs(cdScFiltAnnot)

pdf('Highest_expression_50Gene.pdf')
plotHighestExprs(cdScFiltAnnot)
dev.off()
null device 
          1 

Check identities of the most highly expressed genes before filtering them. This should generally be dominated by constitutively expressed transcripts, such as those for ribosomal or mitochondrial proteins. The presence of other classes of features may be cause for concern if they are not consistent with expected biology. For example, the absence of ribosomal proteins and/or the presence of their pseudogenes are indicative of suboptimal alignment.

Plot shows percentage of total counts (per cell) assigned to the top 50 most highly-abundant features in the dataset.

For each feature, each bar represents the percentage assigned to that feature for a cell, while the circle represents the average across all cells. Bars are coloured by the total number of expressed features in each cell, while circles are coloured according to whether the feature is labelled as a control feature.

Do the topmost genes appear to agree with the biology?

NOTE permanent change to cdScFiltAnnot next!

This mean-based filter tends to be less aggressive. A gene will be retained as long as it has sufficient expression in any subset of cells. Genes expressed in fewer cells require higher levels of expression in those cells to be retained, but this is not undesirable as it avoids selecting uninformative genes (with low expression in few cells) that contribute little to downstream analyses, e.g., HVG detection or clustering. In contrast, the “at least n” filter depends heavily on the choice of n. With n = 10, a gene expressed in a subset of 9 cells would be filtered out, regardless of the level of expression in those cells. This may result in the failure to detect rare subpopulations that are present at frequencies below n. While the mean-based filter will retain more outlier-driven genes, this can be handled by choosing methods that are robust to outliers in the downstream analyses. Thus, we apply the mean-based filter to the data by subsetting the SCESet object as shown below.

ave.counts <- rowMeans(counts(cdScFilt))
keep <- ave.counts >= 0.01
print(paste0('Log10 av count adjustment now leaves ',as.character(sum(keep)), ' genes '))
#sum(keep)
cdScFilt <- cdScFilt[keep,]
cdScFiltAnnot <- cdScFilt
#cdScFiltAnnot <- calculateQCMetrics(cdScFiltAnnot)
cdScFiltAnnot
class: SingleCellExperiment 
dim: 13194 1825 
metadata(0):
assays(2): counts logcounts
rownames(13194): AL669831.5 LINC00115 ... AL354822.1 AC240274.1
rowData names(9): symbol is_feature_control_Mt ... total_counts log10_total_counts
colnames(1825): CGAGTGCTCAGCTAGT-4 TTCTGTATCCACACAA-4 ... TTGCATTTCACCGGGT-1 TGGGCTGGTCAACCTA-3
colData names(39): barcode Sample ... pct_counts_in_top_500_features CellCycle
reducedDimNames(0):
spikeNames(0):

The filtering now lets see how the total counts and features plots:

p <- plotColData(cdScFilt, x="log10_total_counts",y="total_features_by_counts", colour_by = "Sample")  + scale_fill_manual(values=c_sample_col) 
Scale for 'fill' is already present. Adding another scale for 'fill', which will replace the existing scale.
p

What is the frequency of the cells with mean expression?:

plotExprsFreqVsMean(cdScFiltAnnot)

Frequency of the cells versus mean expression. Preferably at least 1000 genes will be expressed in 50% of the cells. These uniformly expressed genes are not helpful in disciminating cells, but if the number of genes expressed in >50% is low, that means the dataset quality is not great because too many dropouts.

save.image()

So things look fine in QC.

6. Normalization of read counts

Single cell RNA-seq data requires different normalisation to bulk data methods (e.g. DESeq2) because scRNA-seq is very sparse. Here the deconvolution based method will be used.

Further detail on the deconvolution method to deal with zero counts: Read counts are subject to differences in capture efficiency and sequencing depth between cells (Stegle et al., 2015). Normalization is required to eliminate these cell-specific biases prior to downstream quantitative analyses. In bulk data this is often done by assuming that most genes are not differentially expressed (DE) between cells. Any systematic difference in count size across the non-DE majority of genes between two cells is assumed to represent bias and is removed by scaling. More specifically, “size factors” are calculated that represent the extent to which counts should be scaled in each library. Single-cell data can be problematic due to the dominance of low and zero counts. To overcome this, counts from many cells are pooled to increase the count size for accurate size factor estimation (Lun et al., 2016). Pool-based size factors are then “deconvolved” into cell-based factors for cell-specific normalization.

cdScFiltAnnot <- computeSumFactors(cdScFiltAnnot, sizes=c(10, 50, 100, 150))
summary(sizeFactors(cdScFiltAnnot))
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.1175  0.6728  0.9386  1.0000  1.2652  3.0557 

If the size factors are tightly correlated with the library sizes for all cells this would suggests that the systematic differences between cells are primarily driven by differences in capture efficiency or sequencing depth. Any DE between cells would yield a non-linear trend between the total count and size factor, and/or increased scatter around the trend.

If there is an error “estimated negative size factor”, then the sizes has to be adjusted__

DF <- data.frame(VAR1=sizeFactors(cdScFiltAnnot), VAR2=cdScFiltAnnot$total_counts/1e6)
model = lm(VAR2 ~ VAR1, DF)
summary(model)

Call:
lm(formula = VAR2 ~ VAR1, data = DF)

Residuals:
       Min         1Q     Median         3Q        Max 
-0.0034400 -0.0006346 -0.0001271  0.0005131  0.0048120 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) 4.790e-04  5.481e-05   8.739   <2e-16 ***
VAR1        6.669e-03  4.966e-05 134.293   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.0009909 on 1823 degrees of freedom
Multiple R-squared:  0.9082,    Adjusted R-squared:  0.9081 
F-statistic: 1.803e+04 on 1 and 1823 DF,  p-value: < 2.2e-16
sp = ggplot(DF, aes(x=VAR1, y=VAR2))
sp + geom_point() + stat_smooth(method=lm) + theme_light(base_size=15) +
        theme(strip.background = element_blank(),
              panel.border     = element_blank(),
              plot.title = element_text(hjust = 0.5)) +  
              xlab("Size Factor") + 
              ylab("Library Size (millions)")+
              annotate("text", label="R^2=0.90", x=2.8, y=0.025)

If the size factors are tightly correlated with the library sizes for all cells this would suggests that the systematic differences between cells are primarily driven by differences in capture efficiency or sequencing depth. Any DE between cells would yield a non-linear trend between the total count and size factor, and/or increased scatter around the trend.

With a high R^2 value, the size factors are very tightly correlated with the library size. I calculate the size factor normization and put it in a slot of SummarizedExperiment object.

Applying the size factors to normalize gene expression:

The count data are used to compute normalized log-expression values for use in downstream analyses. Each value is defined as the log-ratio of each count to the size factor for the corresponding cell, after adding a prior count of 1 to avoid undefined values at zero counts. Division by the size factor ensures that any cell-specific biases are removed. If spike-in-specific size factors are present in sce, they will be automatically applied to normalize the spike-in transcripts separately from the endogenous genes.

Re-calculate the CPM normalization using size factors

Recalculate the counts-per-million for these cells. This is because large number of genes have been filtered out which would have impacted the library size when CPM was calcualted with unfiltered data. Now, as the number of genes have been reduced, so is the library size.

# cpm(cdScFiltAnnot) <- log2(calculateCPM(cdScFiltAnnot, use_size_factors = TRUE) + 1)
exprs(cdScFiltAnnot) <- log2(calculateCPM(cdScFiltAnnot, use_size_factors = TRUE) + 1)

Setting the parameter use.size.factors = TRUE enables the use of step sizes calculated in the above pool based method.

The log-transformation provides some measure of variance stabilization (Law et al., 2014), so that high-abundance genes with large variances do not dominate downstream analyses. The computed values are stored as an exprs matrix in addition to the other assay elements.

Checking for important technical factors

We check whether there are technical factors that contribute substantially to the heterogeneity of gene expression. If so, the factor may need to be regressed out to ensure that it does not inflate the variances or introduce spurious correlations. For this dataset, the simple experimental design means that there are no plate or batch effects to examine. However there could be other technical factors like the cell-cycle effect or dropouts.

Before normalization.

plotExplanatoryVariables(cdScFiltAnnot, exprs_values = "counts", variables=c("total_features_by_counts", "total_counts", "CellCycle", "pct_counts_Mt", "Sample"))

After normalization

plotExplanatoryVariables(cdScFiltAnnot, variables=c("total_features_by_counts", "total_counts", "CellCycle", "pct_counts_Mt", "Sample"))

Assessing this plot. Typically the number of total features (genes) and total counts explain the major variability in scRNA-seq datasets. This indicates that cells vary significantly based on number of genes that they are expressing.

Number of total features explaining majority of the variability is a common issue in single-cell RNA-seq. This could be due to the sparsity of the data. The PCA plot might be separating cells just because they have very different number of features expressed in total.

However, this would not confound the t-SNE plot which would take the non-linear relationship between the variables. As long as the peaks for total_counts and total_feature_by_counts are below 10 this is normal.

In the cell-cycle plot shown earlier and this plot help establish the importance of the cell-cycle effect.

7. Identifying genes for feature selection (HVG)

The goal is to identify genes that will be good features to discriminate the cells. In this step, the dataset is filtered to keep only genes that are “informative” of the variability in the data. Highly Variable Genes (HVG) are the ones driving heterogeneity across the population of cells.

Detail HVG identification requires estimation of the variance in expression for each gene, followed by decomposition of the variance into biological and technical components. HVGs are then identified as those genes with the largest biological components. This avoids prioritizing genes that are highly variable due to technical factors, such as sampling noise during RNA capture and library preparation.

In the recent implementation of seurat, Rahul Satija took a slightly different approach for HVG calculation. They calculate the variance and mean for each gene in the dataset (storing this in object@hvg.info), and sorts genes by their variance/mean ratio (VMR). They have observed that for large-cell datasets with unique molecular identifiers, selecting highly variable genes (HVG) simply based on VMR is an efficient and robust strategy.

But in the implementation used here a trend is fitted to the variance estimates of the endogenous genes, using the use.spikes=FALSE setting as shown below. This assumes that the majority of genes are not variably expressed, such that the technical component dominates the total variance for those genes. The fitted value of the trend is then used as an estimate of the technical component.

span: Low-abundance genes with mean log-expression below min.mean are not used in trend fitting, to preserve the sensitivity of span-based smoothers at moderate-to-high abundances. It also protects against discreteness, which can interfere with estimation of the variability of the variance estimates and accurate scaling of the trend. The default threshold is chosen based on the point at which discreteness is observed in variance estimates from Poisson-distributed counts. For heterogeneous droplet data, a lower threshold of 0.001-0.01 should be used.

var.fit <- trendVar(cdScFiltAnnot, method="loess", use.spikes=FALSE, min.mean=1.0)
var.out <- decomposeVar(cdScFiltAnnot, var.fit)
var.out
DataFrame with 13194 rows and 6 columns
                         mean             total                bio              tech              p.value                 FDR
                    <numeric>         <numeric>          <numeric>         <numeric>            <numeric>           <numeric>
AL669831.5  0.292413514882491  1.99034767547437  0.122567710698052  1.86777996477632   0.0255307580212507   0.085128334933632
LINC00115  0.0823733117556895  0.59071644032303 0.0645601120446376 0.526156328278392 0.000175198141823844 0.00366914965591079
AL645608.3  0.553846919173677   3.7237733200891   0.18609764322652  3.53767567686258   0.0579080622147651    0.15576737469146
AL645608.1  0.226198410062083  1.57852496443396  0.133691408953467  1.44483355548049    0.003229667947632  0.0223686293443867
SAMD11     marker10.set  9.29639140577559  0.567231780948326  8.72915962482726   0.0266547011760268  0.0877669396846761
...                       ...               ...                ...               ...                  ...                 ...
AC011043.1  0.198055496510842   1.3494896050325 0.0844178163860876  1.26507178864641   0.0236829876078569  0.0809097199632479
AL592183.1  0.137653095415761 0.982883638780493  0.103629840233184 0.879253798547309 0.000291592459346225  0.0051365432691777
AC007325.4  0.148623515793144  1.01923733257323 0.0699104001138806  0.94932693245935   0.0145343682695095  0.0583586290164054
AL354822.1  0.266868059934152  1.82490181947289  0.120292455627167  1.70460936384572    0.018132380880379  0.0679042138475061
AC240274.1   0.39116568217776  2.66049513671405  0.161939540289978  2.49855559642407   0.0269561458992432  0.0885385583755577

We assess the suitability of the trend fitted to the endogenous variances by examining whether it is consistent with the variances. The trend passes through or close to most of the endogenous gene variances, indicating that our assumption (that most genes have low levels of biological variability) is valid. This strategy exploits the large number of endogenous genes to obtain a stable trend.

plot(var.out$mean, var.out$total, pch=16, cex=0.6, xlab="Mean log-expression", ylab="Variance of log-expression")
o <- order(var.out$mean)
lines(var.out$mean[o], var.out$tech[o], col="dodgerblue", lwd=2)

Ideally the plot above would look like the mountain, where it would have rise in the middle but then drop off at the end (low variance for highly expressed genes). The trend line would follow it as well.

HVG are defined as genes with biological components that are significantly greater than zero at a false discovery rate (FDR) of 5%. These genes are interesting as they drive differences in the expression profiles between cells, and should be prioritized for further investigation. Here a gene is considered to be a HVG if it has a biological component greater than or equal to 0.5. For transformed expression values on the log2 scale, this means that the average difference in true expression between any two cells will be at least 2-fold. (This reasoning assumes that the true log-expression values are Normally distributed with variance of 0.5. The root-mean-square of the difference between two values is treated as the average log2-fold change between cells and is equal to unity.) The results are ranked by the biological component to focus on genes with larger biological variability. HVG can number anywhere between 200-5000 depending on the complexity of the dataset and depth of sequencing https://www.embopress.org/doi/10.15252/msb.20188746

hvg.out <- var.out[which(var.out$FDR <= 0.05 & var.out$bio >= 0.5),]
hvg.out <- hvg.out[order(hvg.out$bio, decreasing=TRUE),]
print(paste0('Number of HVG using this setting is ' ,as.character(nrow(hvg.out))))
[1] "Number of HVG using this setting is 351"
head(rownames(hvg.out),20)
 [1] "TIMP1"    "KRT81"    "TPT1"     "FN1"      "SAA1"     "IFI27"    "TAGLN"    "MT1X"     "TGM2"     "KRT19"    "AKAP12"   "HIST1H4C" "RARRES1" 
[14] "MT1E"     "CENPF"    "SERPINE1" "TFPI2"    "IGFBP5"   "CCDC80"   "IGFBP4"  

Plotting the HVGs. The red dots are the HVGs we selected.

plot(var.out$mean, var.out$total, pch=16, cex=0.6, xlab="Mean log-expression",
     ylab="Variance of log-expression")
o <- order(var.out$mean)
lines(var.out$mean[o], var.out$tech[o], col="dodgerblue", lwd=2)
points(var.out[rownames(hvg.out),]$mean, var.out[rownames(hvg.out),]$total, col="red", pch=16, cex=0.6)

Plotting the top 15 HVGs:

plotExpression(cdScFiltAnnot, rownames(hvg.out)[1:15])

Discuss meaning of genes.

write.table(file="HVG.csv", hvg.out, sep="\t", quote = FALSE, col.names = NA)
head(hvg.out, 20)
DataFrame with 20 rows and 6 columns
                     mean            total              bio             tech               p.value                   FDR
                <numeric>        <numeric>        <numeric>        <numeric>             <numeric>             <numeric>
TIMP1    7.86709632160163 24.2773947708697  14.521193749592 9.75620102127772  3.1982518755065e-231 2.10988676227164e-227
KRT81    4.13544171576234 27.1241059275866 12.1073346733105 15.0167712542761  1.14030725678216e-87  3.00904278919677e-84
TPT1     10.1176738428334 17.5285486496608 10.5118712976971 7.01667735196367 1.57150703640845e-233 2.07344638383731e-229
FN1      7.71819936714799 20.4358311308409 10.4574221250506 9.97840900579027 8.77594389293711e-134 3.85966012411374e-130
SAA1      3.4789139910594 23.1964357545348 8.66293417245515 14.5335015820797  2.74142138367708e-53  5.16718767660505e-50
...                   ...              ...              ...              ...                   ...                   ...
SERPINE1 8.23324339691744 14.5150273844362 5.25662237460789 9.25840500982828   3.8182732832782e-49  4.57984524541569e-46
TFPI2    5.20727951678479 19.4585282871293 5.22059050012891 14.2379377870004  1.11684643484008e-23  5.08126615906208e-21
IGFBP5   2.01236389552981   16.15402191995  5.2132931685508 10.9407287513992  1.12140008431223e-36  7.78723826969242e-34
CCDC80   5.54682811043964 18.6483909082604 5.05734129870008 13.5910496095604  2.88693569741106e-24  1.36036534255863e-21
IGFBP4   4.59939001023172 19.9203468614219 4.95178985026739 14.9685570111545  5.79621466592908e-20  2.06689881898022e-17

Final Notes There are alternative approaches for determining the HVG, sspecially those based on Coefficient of Variance. The method used here, the variance of the log-expression values, avoids genes with strong expression in only one or two cells. This ensures that the set of top HVGs is not dominated by genes with (mostly uninteresting) outlier expression patterns.

However, it has been mentioned that fitting the trendline to endogenous genes might not always be a good idea.

8. Analysis of the dataset using highly variable genes HVG

1. PCA
2. t-SNE
3. UMAP
rowVarsSorted <- exprs(cdScFiltAnnot)[rownames(hvg.out),]
FinalPCAData <- t(rowVarsSorted)
pcaPRComp <- prcomp(FinalPCAData)
nmax = 10
if(dim(pcaPRComp$x)[1] < 10){ nmax = dim(pcaPRComp$x)[1] }
txt1 <- paste("Percent_PC_Var_onfirst",nmax,"PCs",sep="")
pca_var = pcaPRComp$sdev ^ 2
pca_var_percent <- 100 * pca_var / sum(pca_var)
pca_var_percent_first10 <- NA * pca_var
pca_var_percent_first10[1:nmax] <- 100 * pca_var[1:nmax] / sum(pca_var[1:nmax])
#pca_corr_reads <- apply(pcaPRComp$x,2,function(x) cor(x,report_sub$Assigned))
    
pca_var_out <- data.frame(round(pca_var,3),round(pca_var_percent,1),
                          round(pca_var_percent_first10,1))
#rownames(pca_var_out) <- rownames(pcaPRComp$x)
colnames(pca_var_out) <- c("PC_Var","PC_Var_percent",txt1)
nColToDisplay = 5
df <- as.data.frame(pcaPRComp$x)
df$Cell=as.factor(colData(cdScFiltAnnot)$Sample)
p <- ggpairs(df, columns=1:nColToDisplay, upper=list(continuous="points"), 
             title='Plotting first four PCAs', 
             mapping = aes_string(color="Cell"),
             legend = c(1,nColToDisplay),
             columnLabels = as.character(paste0(colnames(df[,1:nColToDisplay]), ' : ', 
                                                pca_var_out$PC_Var_percent[1:nColToDisplay], '% variance')))+
    theme_light(base_size=15)+     
    theme(plot.title = element_text(hjust = 0.5))
for(i in 1:p$nrow) {
  for(j in 1:p$ncol){
    p[i,j] <- p[i,j] + 
        scale_fill_manual(values=c_sample_col) +
        scale_colour_manual(values=c_sample_col)  
  }
}
    
print(p)

#PC 1v2
df_out <- as.data.frame(pcaPRComp$x)
df_out$group <- sapply( strsplit(as.character(row.names(df)), "_"), "[[", 1 )
Samples=as.factor(colData(cdScFiltAnnot)$Sample)
pp12<-ggplot(df_out,aes(x=PC1,y=PC2,color=Samples ))
pp12<-pp12+geom_point()+ 
        scale_colour_manual(values=c_sample_col)+
    xlab(paste0("PC1: ",round(pca_var_percent_first10[1],1),"% variance")) +
    ylab(paste0("PC2: ",round(pca_var_percent_first10[2],1),"% variance")) + theme_classic()
#pca_var_percent_first10[1] <- 100 * pca_var[1:nmax] / sum(pca_var[1:nmax])
#PC 3v4
df_out <- as.data.frame(pcaPRComp$x)
df_out$group <- sapply( strsplit(as.character(row.names(df)), "_"), "[[", 1 )
Samples=as.factor(colData(cdScFiltAnnot)$Sample)
pp34<-ggplot(df_out,aes(x=PC3,y=PC4,color=Samples ))
pp34<-pp34+geom_point()+ 
        scale_colour_manual(values=c_sample_col)+
    xlab(paste0("PC3: ",round(pca_var_percent_first10[3],1),"% variance")) +
    ylab(paste0("PC4: ",round(pca_var_percent_first10[4],1),"% variance")) + theme_classic()
multiplot(pp12,pp34, cols=1)

t-SNE plot with only the HVG genes:

We first calculate how much of variance each PC is giving

plot(c(1:20), pca_var_percent[1:20])

 pca_var_percent[1:20]
 [1] 11.2078941  8.2847607  4.6640806  3.9525467  3.1620849  1.8311175  1.1426589  0.9681960  0.7748274  0.6650499  0.6611703  0.6020669  0.5856361
[14]  0.5575728  0.5308503  0.5065127  0.4949544  0.4697527  0.4527313  0.4434847

Note t-SNE calculated next. Everytime you run this step the t-SNE plot will look different.

tsne_out <- Rtsne(as.matrix(pcaPRComp$x[,1:10]),check_duplicates = FALSE, pca = FALSE,
             perplexity=30, theta=0.01, dims=2, num_threads = 8)
Rep <- as.factor(colData(cdScFiltAnnot)$Sample)
counts <- colData(cdScFiltAnnot)$total_counts
p <- ggplot(as.data.frame(tsne_out$Y), aes(x=V1, y=V2, color=counts)) +
     geom_point(size=0.75) +
     guides(colour = guide_legend(override.aes = list(size=0.5))) +
     xlab("") + ylab("") +
     ggtitle("t-SNE 2D Embedding of Sample Data") +
     theme_classic(base_size=14) +
     theme(strip.background = element_blank(),
           strip.text.x     = element_blank(),
           axis.text.x      = element_blank(),
           axis.text.y      = element_blank(),
           axis.ticks       = element_blank(),
           axis.line        = element_blank(),
           panel.border     = element_blank())
p2 <- ggplot(as.data.frame(tsne_out$Y), aes(x=V1, y=V2, color=Rep)) +
     geom_point(size=0.75) +
     guides(colour = guide_legend(override.aes = list(size=0.8))) +
     xlab("") + ylab("") +
     ggtitle("t-SNE 2D Embedding of Expression Data") +
     theme_classic(base_size=10) +
     theme(strip.background = element_blank(),
           strip.text.x     = element_blank(),
           axis.text.x      = element_blank(),
           axis.text.y      = element_blank(),
           axis.ticks       = element_blank(),
           axis.line        = element_blank(),
           panel.border     = element_blank()) +
          scale_fill_manual(values=c_sample_col) +
         scale_colour_manual(values=c_sample_col)
p2

#multiplot(p,p2,cols=2)
reducedDim(cdScFiltAnnot,'tSNE') <- tsne_out$Y

Running UMAP for data visualization

embedding <- umap(pcaPRComp$x[,1:10])
as.tibble(embedding$layout) %>%
  mutate(Samples = colData(cdScFiltAnnot)$Sample) %>%
  ggplot(aes(V1, V2, color=Samples)) +  geom_point(size=0.75) + theme_classic() +scale_colour_manual(values=c_sample_col)

reducedDim(cdScFiltAnnot,'UMAP') <- embedding$layout
save.image()

9. Clustering cells

The goal is to split the cells in the dataset into clusters, such that: 1. the cells in the same cluster are as similar as possible, 2. the cells in different clusters are highly distinct

There are numerous methods of clustering; dynamic-cut-tree, k-means, k-medoids, GMM, GP-LVM and so on. Here the dynamic-cut-tree will be used.

Dynamic Cut Tree Description

Hierarchical clustering is a widely used method for detecting clusters in genomic data. Clusters are defined by cutting branches off the dendrogram. A common but inflexible method uses a constant height cutoff value; this method exhibits suboptimal performance on complicated dendrograms. The Dynamic Tree Cut R library implements novel dynamic branch cutting methods for detecting clusters in a dendrogram depending on their shape. Compared to the constant height cutoff method, these techniques offer the following advantages: (1) they are capable of identifying nested clusters; (2) they are flexible — cluster shape parameters can be tuned to suit the application at hand; (3) they are suitable for automation; and (4) they can optionally combine the advantages of hierarchical clustering and partitioning around medoids, giving better detection of outliers.

Clustering cells into putative subpopulations

Hierarchical clustering is performed on the Euclidean distances between cells, using Ward’s criterion to minimize the total variance within each cluster. This yields a dendrogram that groups together cells with similar expression patterns across the chosen genes.

Clusters are explicitly defined by applying a dynamic tree cut (Langfelder et al., 2008)[https://academic.oup.com/bioinformatics/article/24/5/719/200751] to the dendrogram. This exploits the shape of the branches in the dendrogram to refine the cluster definitions, and is more appropriate than cutree for complex dendrograms. Greater control of the empirical clusters can be obtained by manually specifying cutHeight in cutreeDynamic.

An alternative approach is to cluster on a matrix of distances derived from correlations (e.g., as in quickCluster). This is more robust to noise and normalization errors, but is also less sensitive to subtle changes in the expression profiles.

chosen.exprs <- logcounts(cdScFiltAnnot[rownames(hvg.out),])
my.dist <- dist(t(chosen.exprs))
my.tree <- hclust(my.dist, method = "ward.D2")
my.clusters <- unname(cutreeDynamic(my.tree, distM=as.matrix(my.dist), verbose=0))
print(paste0('Number of clusters'))
[1] "Number of clusters"
levels(as.factor(my.clusters))
[1] "1" "2" "3" "4" "5" "6" "7"
cdScFiltAnnot$Clusters <- as.factor(my.clusters)

Drawing the heatmap, first scale te:

heat.vals <- chosen.exprs - rowMeans(chosen.exprs)
#clust.col <- rainbow(max(my.clusters))
#heatmap.2(heat.vals, col=bluered, symbreak=TRUE, trace='none', cexRow=0.3,
#    ColSideColors=clust.col[my.clusters], Colv=as.dendrogram(my.tree))
df = data.frame(Class = as.factor(my.clusters))
ha = HeatmapAnnotation(df = df)
#ha = HeatmapAnnotation(df = df, col = list(Condition = c("MC_A" =  "dodgerblue", "MC_B"= "firebrick", "MC_C"="forestgreen", "MC_D"="gold")))
Heatmap(heat.vals , top_annotation = ha, show_column_names=FALSE, cluster_rows = TRUE, clustering_method_columns = "ward.D2", row_names_gp = gpar(fontsize = 8))

Saving the heatmap for later exploration:

pdf("heatmap.pdf", width=20, height=40)
Heatmap(heat.vals , top_annotation = ha, show_column_names=FALSE, cluster_rows = TRUE, clustering_method_columns = "ward.D2", row_names_gp = gpar(fontsize = 8))
dev.off()
null device 
          1 

t-SNE with cluster allocation:

## t-SNE with cluster allocation
Clusters <- as.factor(my.clusters)
p1 <- ggplot(as.data.frame(tsne_out$Y), aes(x=V1, y=V2, color=Clusters)) +
     geom_point(size=1.0) +
     guides(colour = guide_legend(override.aes = list(size=1))) +
     xlab("") + ylab("") +
     ggtitle("t-SNE 2D coloured by clusters") +
     theme_classic(base_size=10) +
     theme(strip.background = element_blank(),
           strip.text.x     = element_blank(),
           axis.text.x      = element_blank(),
           axis.text.y      = element_blank(),
           axis.ticks       = element_blank(),
           axis.line        = element_blank(),
           panel.border     = element_blank()) + scale_color_manual(values=c_clust_col)
p1

#multiplot(p,p2,cols=2)
## t-SNE with Cell-cycle
CellCycle <- as.factor(cdScFiltAnnot$CellCycle)
p2 <- ggplot(as.data.frame(tsne_out$Y), aes(x=V1, y=V2, color=CellCycle)) +
     geom_point(size=1.0) +
     guides(colour = guide_legend(override.aes = list(size=1))) +
     xlab("") + ylab("") +
     ggtitle("t-SNE 2D coloured by cell-cycle") +
     theme_classic(base_size=10) +
     theme(strip.background = element_blank(),
           strip.text.x     = element_blank(),
           axis.text.x      = element_blank(),
           axis.text.y      = element_blank(),
           axis.ticks       = element_blank(),
           axis.line        = element_blank(),
           panel.border     = element_blank()) 
p2

#multiplot(p,p2,cols=2)
## t-SNE with Sample
Sample <- as.factor(cdScFiltAnnot$Sample)
p3 <- ggplot(as.data.frame(tsne_out$Y), aes(x=V1, y=V2, color=Sample)) +
     geom_point(size=1.0) +
     guides(colour = guide_legend(override.aes = list(size=1))) +
     xlab("") + ylab("") +
     ggtitle("t-SNE 2D coloured by sample") +
     theme_classic(base_size=10) +
     theme(strip.background = element_blank(),
           strip.text.x     = element_blank(),
           axis.text.x      = element_blank(),
           axis.text.y      = element_blank(),
           axis.ticks       = element_blank(),
           axis.line        = element_blank(),
           panel.border     = element_blank()) 
p3

## t-SNE Clustering impacted with read-counts
p4 <- as.tibble(as.data.frame(tsne_out$Y)) %>%
  mutate(ReadDepth = cdScFiltAnnot$log10_total_counts) %>%
  ggplot(aes(V1, V2, color=ReadDepth)) +  geom_point(size=0.75) + theme_classic() +
  scale_colour_gradientn(colours = rainbow(5)) + 
  theme(strip.background = element_blank(),
           strip.text.x     = element_blank(),
           axis.text.x      = element_blank(),
           axis.text.y      = element_blank(),
           axis.ticks       = element_blank(),
           axis.line        = element_blank(),
           panel.border     = element_blank()) 
p4

multiplot(p1,p2,p3,p4, cols=2)

Clusters <- as.factor(my.clusters)
as.tibble(embedding$layout) %>%
  mutate(Clusters = Clusters) %>%
  ggplot(aes(V1, V2, color=Clusters)) +  geom_point(size=0.75) + theme_classic() + scale_color_manual(values=c_clust_col)

as.tibble(embedding$layout) %>%
  mutate(Samples = colData(cdScFiltAnnot)$Sample) %>%
  ggplot(aes(V1, V2, color=Samples)) +  geom_point(size=0.75) + theme_classic() + scale_color_manual(values=c_clust_col)

Silhouette cluster validation plot

The silhouette plot displays a measure of how close each point in one cluster is to points in the neighboring clusters. Silhouette width can be interpreted as follows: - Cells with a large Si (almost 1) are very well clustered. - A small Si (around 0) means that the observation lies between two clusters. - Cells with a negative Si are probably placed in the wrong cluster. (so cluster is less stable and trustworthy)

Background to cluster validation

Internal measures for cluster validation

In this section, we describe the most widely used clustering validation indices. Recall that the goal of partitioning clustering algorithms (Part @ref(partitioning-clustering)) is to split the data set into clusters of objects, such that:

Internal validation measures often reflect the compactness, the connectedness and the separation of the cluster partitions.

  1. Compactness or cluster cohesion: Measures how close are the objects within the same cluster. A lower within-cluster variation is an indicator of a good compactness (i.e., a good clustering). The different indices for evaluating the compactness of clusters are base on distance measures such as the cluster-wise within average/median distances between observations.
  2. Separation: Measures how well-separated a cluster is from other clusters. The indices used as separation measures include:
    • distances between cluster centers
    • the pairwise minimum distances between objects in different clusters
  3. Connectivity: corresponds to what extent items are placed in the same cluster as their nearest neighbors in the data space. The connectivity has a value between 0 and infinity and should be minimized.

Generally most of the indices used for internal clustering validation combine compactness and separation measures as follow:

\(Index = \frac{\alpha * Seperation}{\beta * Compactness}\) where \(\alpha\) and \(\beta\) are weights.

Silhouette coefficient

The silhouette analysis measures how well an observation is clustered and it estimates the average distance between clusters. The silhouette plot displays a measure of how close each point in one cluster is to points in the neighboring clusters.

For each observation \(i\), the silhouette width \(s_i\) is calculated as follows:

  1. For each observation \(i\), calculate the average dissimalirty \(\alpha_i\) between \(i\) and all other points of the cluster which \(i\) belongs.
  2. For all other clusters \(C\), to which \(i\) does not belong, calculate the average dissimilarity \(d(i,C)\) of \(i\) to all observations of \(C\). The smallest of these \(d(i,C)\) is defined as \(b_i = min_C d(i,C)\). The value of \(b_i\) can be seen as the dissimilarity between i and its neighbor cluster, i.e. the nearest one to which it does not belong.
  3. Finally the silhouette width of the observation \(i\) is defined by the formula: \(S_i = \frac{(b_i - a_i)}{max(a_i,b_i)}\)

Silhouette width can be interpreted as follow:

  • Observations with a large Si (almost 1) are very well clustered.
  • A small Si (around 0) means that the observation lies between two clusters.
  • Observations with a negative Si are probably placed in the wrong cluster.
#my.clusters <- unname(cutreeDynamic(my.tree, distM=as.matrix(my.dist), verbose=0))
fviz_silhouette(silhouette(my.clusters, my.dist), print.summary = FALSE)

It looks like that the stability of cluster 5 is quite low. Something to keep in mind.

Now, how does the TP53 and XIST expression looks like

GeneExp <- logcounts(cdScFiltAnnot)['XIST',]
    #GeneName = 'SPN'
    df <- as.data.frame(tsne_out$Y)
    df[,'GeneExp']=logcounts(cdScFiltAnnot)['XIST',]
p1<-     ggplot(df, aes(x=V1, y=V2, GeneExp = GeneExp)) +
      geom_point(size=1.00,aes(colour = GeneExp), alpha=0.8) +
      #scale_colour_viridis_c()+
      scale_colour_gradient(low = "gray88", high = "red")+
      #guides(colour = guide_legend(override.aes = list(size=4))) +
      xlab("") + ylab("") +
      ggtitle(paste0('Gene Exp:','XIST'))+
      theme_classic(base_size=14) +
      theme(strip.background = element_blank(),
            strip.text.x     = element_blank(),
            axis.text.x      = element_blank(),
            axis.text.y      = element_blank(),
            axis.ticks       = element_blank(),
            axis.line        = element_blank(),
            panel.border     = element_blank())
p1

GeneExp <- logcounts(cdScFiltAnnot)['XIST',]
    #GeneName = 'SPN'
    df <- as.data.frame(tsne_out$Y)
    df[,'GeneExp']=logcounts(cdScFiltAnnot)['XIST',]
p1<-     ggplot(df, aes(x=V1, y=V2, GeneExp = GeneExp)) +
      geom_point(size=1.00,aes(colour = GeneExp), alpha=0.8) +
      #scale_colour_viridis_c()+
      scale_colour_gradientn(colours = hcl.colors(n=4, palette = 'RdYlBu'))+
      #guides(colour = guide_legend(override.aes = list(size=4))) +
      xlab("") + ylab("") +
      ggtitle(paste0('Gene Exp:','XIST'))+
      theme_classic(base_size=14) +
      theme(strip.background = element_blank(),
            strip.text.x     = element_blank(),
            axis.text.x      = element_blank(),
            axis.text.y      = element_blank(),
            axis.ticks       = element_blank(),
            axis.line        = element_blank(),
            panel.border     = element_blank())
p1

GeneExp <- logcounts(cdScFiltAnnot)['XIST',]
    #GeneName = 'SPN'
    df <- as.data.frame(tsne_out$Y)
    df[,'GeneExp']=logcounts(cdScFiltAnnot)['XIST',]
p1<-     ggplot(df, aes(x=V1, y=V2, GeneExp = GeneExp)) +
      geom_point(size=1.00,aes(colour = GeneExp), alpha=0.8) +
  scale_colour_viridis_c()+
      #scale_colour_gradient(low = "gray88", high = "purple4")+
      #guides(colour = guide_legend(override.aes = list(size=4))) +
      xlab("") + ylab("") +
      ggtitle(paste0('Gene Exp:','XIST'))+
      theme_classic(base_size=14) +
      theme(strip.background = element_blank(),
            strip.text.x     = element_blank(),
            axis.text.x      = element_blank(),
            axis.text.y      = element_blank(),
            axis.ticks       = element_blank(),
            axis.line        = element_blank(),
            panel.border     = element_blank())
p1

GeneExp <- logcounts(cdScFiltAnnot)['TP53',]
    #GeneName = 'SPN'
    df <- as.data.frame(tsne_out$Y)
    df[,'GeneExp']=logcounts(cdScFiltAnnot)['TP53',]
  p2 <-  ggplot(df, aes(x=V1, y=V2, GeneExp = GeneExp)) +
      geom_point(size=1.00,aes(colour = GeneExp), alpha=0.3) +
      scale_colour_gradient(low = "gray88", high = "purple4")+
      #guides(colour = guide_legend(override.aes = list(size=4))) +
      xlab("") + ylab("") +
      ggtitle(paste0('Gene Exp:','TP53'))+
      theme_classic(base_size=14) +
      theme(strip.background = element_blank(),
            strip.text.x     = element_blank(),
            axis.text.x      = element_blank(),
            axis.text.y      = element_blank(),
            axis.ticks       = element_blank(),
            axis.line        = element_blank(),
            panel.border     = element_blank())
  p2

Identifying marker genes

Potential marker genes are identified by taking the top set of DE genes from each pairwise comparison between clusters. We arrange the results into a single output table that allows a marker set to be easily defined for a user-specified size of the top set. For example, to construct a marker set from the top 10 genes of each comparison, one would filter marker.set to retain rows with Top less than or equal to 10.

We save the list of candidate marker genes for further examination. We also examine their expression profiles to verify that the DE signature is robust. The heatmap figure below indicates that most of the top markers have strong and consistent up- or downregulation in cells of cluster 1 compared to some or all of the other clusters. Thus, cells from the subpopulation of interest can be identified as those that express the upregulated markers and do not express the downregulated markers.

cluster <- factor(my.clusters)
de.design <- model.matrix(~0 + cluster)
y <- convertTo(cdScFiltAnnot, type="edgeR")
y <- estimateDisp(y, de.design)
fit <- glmFit(y, de.design)
summary(y$tagwise.dispersion)
     Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
  0.00010   0.08264   0.23238   0.71461   0.60001 102.40000 
clust.col <- rainbow(max(my.clusters))
clusterNumber <- max(my.clusters)
pairwiseComparisonNumber <- choose(clusterNumber,2)
compClusters <- length(levels(as.factor(my.clusters)))-1
endClusters <- (3+length(levels(as.factor(my.clusters)))-2)
## setup parallel backend to use many processors
# cores=detectCores()
# cl <- makeCluster(12) #Have choosen 12 cores here
# registerDoParallel(cl)
result.logFC <- result.FDR <- list()
#foreach(i=c(1:(clusterNumber))) %dopar% {
for(i in c(1:(clusterNumber))){
  for(j in c(1:clusterNumber))
  {
    if(i == j){ next }
    print(paste0('In ',i,", in ",j))
    contrast <- numeric(ncol(de.design))
    contrast[i] <- 1
    contrast[j] <- -1
    fit <- edgeR::glmQLFit(y, de.design)
    res <- edgeR::glmQLFTest(fit, contrast = contrast)
    top.tags <- edgeR::topTags(res, n=length(y$design), sort.by="none")
    
    #assign(paste0("UPGenes_",i,"_",j), rownames(topTags$table[topTags$table$FDR < 0.05 & topTags$table$logFC > 1,]))
    #assign(paste0("UPGenes_",j,"_",i), rownames(topTags$table[topTags$table$FDR < 0.05 & topTags$table$logFC < -1,]))
    #UPgenes <- rownames(topTags$table[topTags$table$FDR < 0.05 & topTags$table$logFC > 1,])
    #Downgenes <- rownames(topTags$table[topTags$table$FDR < 0.05 & topTags$table$logFC < -1,])
    
    con.name <- paste0('vs.', levels(cluster)[j])
    result.logFC[[con.name]] <- top.tags$table$logFC
    #names(result1.logFC[[con.name]]) <- rownames(top.tags$table)
    result.FDR[[con.name]] <- top.tags$table$FDR
    #names(result1.FDR[[con.name]]) <- rownames(top.tags$table)
    
  }
  
  
    collected.ranks <- lapply(result.FDR, rank, ties="first")
    min.rank <- do.call(pmin, collected.ranks)
    marker.set <- data.frame(Top=min.rank, Gene=rownames(top.tags),
                              logFC=do.call(cbind, result.logFC), 
                                FDR = result.FDR, stringsAsFactors=FALSE)
    marker.set <- marker.set[order(marker.set$Top),]
    marker.set.pos <- marker.set[rowSums(marker.set[,3:endClusters]>0)==compClusters,]
    marker.set.pos <- marker.set.pos[order(marker.set.pos$Top),]
    write.table(marker.set, file=paste0("SC_workshop_Cluster",i,".tsv"), sep="\t", quote=FALSE, col.names=NA)
    write.table(marker.set.pos, file=paste0("SC_workshop_Cluster",i,"_pos.tsv"), sep="\t", quote=FALSE, col.names=NA)
}
[1] "In 1, in 2"
[1] "In 1, in 3"
[1] "In 1, in 4"
[1] "In 1, in 5"
[1] "In 1, in 6"
[1] "In 1, in 7"
[1] "In 2, in 1"
[1] "In 2, in 3"
[1] "In 2, in 4"
[1] "In 2, in 5"
[1] "In 2, in 6"
[1] "In 2, in 7"
[1] "In 3, in 1"
[1] "In 3, in 2"
[1] "In 3, in 4"
[1] "In 3, in 5"
[1] "In 3, in 6"
[1] "In 3, in 7"
[1] "In 4, in 1"
[1] "In 4, in 2"
[1] "In 4, in 3"
[1] "In 4, in 5"
[1] "In 4, in 6"
[1] "In 4, in 7"
[1] "In 5, in 1"
[1] "In 5, in 2"
[1] "In 5, in 3"
[1] "In 5, in 4"
[1] "In 5, in 6"
[1] "In 5, in 7"
[1] "In 6, in 1"
[1] "In 6, in 2"
[1] "In 6, in 3"
[1] "In 6, in 4"
[1] "In 6, in 5"
[1] "In 6, in 7"
[1] "In 7, in 1"
[1] "In 7, in 2"
[1] "In 7, in 3"
[1] "In 7, in 4"
[1] "In 7, in 5"
[1] "In 7, in 6"
save.image()
LS0tCnRpdGxlOiAiU2luZ2xlIGNlbGwgd29ya3Nob3Agd2l0aCBzY1JOQS1zZXEgZm9yIG92YXJpYW4gY2FuY2VyIG1vZGVsIChzdWJzZXQgb2YgY2VsbHMpIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRoZW1lOiB1bml0ZWQKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogbm8KICAgICAgc21vb3RoX3Njcm9sbDogbm8KICBodG1sX2RvY3VtZW50OgogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICB0b2M6IHllcwotLS0KCgoKIyBQcm9qZWN0IE5FWFRTRVFfIzE1NCAoTGl2ZXIgZGV2ZWxvcG1lbnQpClRoaXMgbm90ZWJvb2sgZG9jdW1lbnRzIHRoZSBzdGVwcyBmb3IgdGhlIGFnZ3JlZ2F0ZWQgZGF0YSB3aXRoIG5vIG5vcm1hbGlzYXRpb24gYnkgQ2VsbHJhbmdlcgpfUmVzZWFyY2hlcjpfIExvdWlzYSBOZWxzb24KX1BJOl8gU3RlcGhlbiBUYXlsb3IKX0FuYWx5c3Q6XyBTeWVkIE11cnR1emEgQmFrZXIKCgojIyBQcm9qZWN0IERlc2NyaXB0aW9uCgpUaGVzZSBzYW1wbGVzIGFyZSBiYXNpY2FsbHkgYW4gYWRkaXRpb24gdG8gIHRoZSBvbmUgc2FtcGxlIGluIHRoZSBwYXBlciB0aGF0IHdhcyBhbmFseXNlZCBwcmV2aW91c2x5LiBUaGV5IGFyZSBjZWxscyBmcm9tIHBhdGllbnRzIHdpdGggb3ZhcmlhbiBjYW5jZXIsIHdlIGlzb2xhdGVkIHRoZSB0dW1vdXIgYW5kIHN0cm9tYWwgY2VsbHMgZm9yIGVhY2ggcGF0aWVudC4gQW5kIGhhdmUgbWl4ZWQgdGhlbSBiYWNrIHRvZ2V0aGVyIHRvIGdpdmUgMSBzYW1wbGUgcGVyIHBhdGllbnQuCgpTbyB0aGUgb25seSBkaWZmZXJlbmNlIGZyb20gdGhlIHByZXZpb3VzIHNhbXBsZSBpcyB0aGF0IHRoZXNlIGhhdmUgdGhlIHR1bW91ciBhbmQgc3Ryb21hbHMgZnJvbSB0aGUgc2FtZSBwYXRpZW50IG1peGVkIHRvZ2V0aGVyIGluIGEgYDc1JToyNSVgIHJhdGlvCgojIyBCYWNrZ3JvdW5kCkhpZ2gtZ3JhZGUgc2Vyb3VzIG92YXJpYW4gY2FyY2lub21hIGlzIGNoYXJhY3RlcmlzZWQgYnkgVFA1MyBtdXRhdGlvbiBhbmQgZXh0ZW5zaXZlIGNocm9tb3NvbWUgaW5zdGFiaWxpdHkgKENJTikuIEJlY2F1c2Ugb3VyIHVuZGVyc3RhbmRpbmcgb2YgQ0lOIG1lY2hhbmlzbXMgaXMgYmFzZWQgbGFyZ2VseSBvbiBhbmFseXNpbmcgZXN0YWJsaXNoZWQgY2VsbCBsaW5lcywgd2UgZGV2ZWxvcGVkIGEgd29ya2Zsb3cgZm9yIGdlbmVyYXRpbmcgZXggdml2byBjdWx0dXJlcyBmcm9tIHBhdGllbnQgYmlvcHNpZXMgdG8gcHJvdmlkZSBtb2RlbHMgdGhhdCBzdXBwb3J0IGludGVycm9nYXRpb24gb2YgQ0lOIG1lY2hhbmlzbXMgaW4gY2VsbHMgbm90IGV4dGVuc2l2ZWx5IGN1bHR1cmVkIGluIHZpdHJvLiBIZXJlLCB3ZSBkZXNjcmliZSBhIOKAnGxpdmluZyBiaW9iYW5r4oCdIG9mIG92YXJpYW4gY2FuY2VyIG1vZGVscyB3aXRoIGV4dGVuc2l2ZSByZXBsaWNhdGl2ZSBjYXBhY2l0eSwgZGVyaXZlZCBmcm9tIGJvdGggYXNjaXRlcyBhbmQgc29saWQgYmlvcHNpZXMuCgpEZXRhaWxzIG9mIHRoZSBwcm9qZWN0IGNhbiBiZSBmb3VuZCBpbiBbTmVsc29uLCBMLiBldC4gYWwuIEEgbGl2aW5nIGJpb2Jhbmsgb2Ygb3ZhcmlhbiBjYW5jZXIgZXggdml2byBtb2RlbHMgcmV2ZWFscyBwcm9mb3VuZCBtaXRvdGljIGhldGVyb2dlbmVpdHkuIE5hdCBDb21tdW4gMTEsIDgyMiAoMjAyMCldKCBodHRwczovL2RvaS5vcmcvMTAuMTAzOC9zNDE0NjctMDIwLTE0NTUxLTIpCgoKIyMgQWltClRoZSBhaW1zIHdlcmUKCiAgICAxLiBUbyBjbHVzdGVyIHRoZW0gZXN0YWJsaXNoIHRoZSAyIHBvcHVsYXRpb25zCiAgICAyLiBUbyBlc3RhYmxpc2ggdGhlIG92ZXItZGlzcGVyc2VkIGdlbmVzIGluIHRoZSB0d28gcG9wdWxhdGlvbnMKCkZyb20gdGhlIGZpcnN0IHNhbXBsZSBgcDUzYCBhbmQgYFhJU1RgIHdlcmUgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIHNvIHlvdSBjb3VsZCB1c2UgdGhlc2UgdG8gdmFsaWRhdGUgdGhlIGluaXRpYWwgY2x1c3RlcmluZy4gVGhlIGFuYWx5c2lzIGFmdGVyIHRoYXQgd291bGQgYmUgdGhlIHNhbWUgYXMgdGhlIHByZXZpb3VzIHNhbXBsZQoKCiMgRGF0YSBwcmVwZXJhdGlvbgpUaGUgZmlyc3QgdGhpbmcgaXMgdGhlIGxvYWQgdGhlIGxpYnJhcmllcyB0aGF0IHdlIHdpbGwgYmUgdXNpbmcgaW4gb3VyIGRvd25zdHJlYW0gYW5hbHlzaXMKCiMjIExvYWRpbmcgbGlicmFyeQpgYGB7cn0KbGlicmFyeShTZXVyYXQpCmxpYnJhcnkoc2NhdGVyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoc2NyYW4pCmxpYnJhcnkoR0dhbGx5KQpsaWJyYXJ5KG1jbHVzdCkKbGlicmFyeShSdHNuZSkKbGlicmFyeSh2aXJpZGlzKQojbGlicmFyeSh1bWFwcikKbGlicmFyeSh1bWFwKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShwYWxldHRlZXIpCmxpYnJhcnkoYmlvbWFSdCkKbGlicmFyeShkeW5hbWljVHJlZUN1dCkKbGlicmFyeShDb21wbGV4SGVhdG1hcCkKbGlicmFyeShjbHVzdGVyKQpsaWJyYXJ5KGZhY3RvZXh0cmEpCmxpYnJhcnkoZWRnZVIpCmBgYAoKYGBge3J9CndyaXRlTGluZXMoY2FwdHVyZS5vdXRwdXQoc2Vzc2lvbkluZm8oKSksICJzZXNzaW9uSW5mby50eHQiKQpzZXNzaW9uSW5mbygpCmBgYAoKClNldHRpbmcgdXAgY29sb3IgcGFsZXR0ZXMuIFRoZSBmb2xsb3dpbmcgY29kZSB3aXRoIHByb2R1Y2UgYSBwaWUgY2hhcnQgd2l0aCBjb2xvdXIgY29uZmlndXJhdGlvbnMuCgpTZXR0aW5nIHVwIGNvbG9yYmxpbmQgZnJpZW5kbHkgY29sb3IgcGFsZXR0ZToKCmBgYHtyfQojY2JQYWxldHRlIDwtIHBhbGV0dGVlcl9kKHBhY2thZ2UgPSAiZ2d0aGVtZXMiLCBwYWxldHRlPSJjYWxjIiwgbj0xMikKIGMzMCA8LSBjKCJkb2RnZXJibHVlMiIsIzEKICAgICAgICAgIiNFMzFBMUMiLCAjMiByZWQKICAgICAgICAgICJncmVlbjQiLCAjMwogICAgICAgICAiI0ZGN0YwMCIsICM0IG9yYW5nZQogICAgICAgICAgICJncmVlbjEiLCM1CiAgICAgICAgICJwdXJwbGUiLCM2CiAgICAgICAgICJibHVlMSIsIzcKICAgICAgICAgImRlZXBwaW5rMSIsIzgKICAgICAgICAgImRhcmtvcmFuZ2U0IiwjOQogICAgICAgICAgImJsYWNrIiwjMTAKICAgICAgICAgImdvbGQxIiwjMTEKICAgICAgICAgImRhcmt0dXJxdW9pc2UiLCMxMgogICAgICAgICAiIzZBM0Q5QSIsICMxMyBwdXJwbGUKICAgICAgICAgIm9yY2hpZDEiLCMxNAogICAgICAgICAiZ3JheTcwIiwjMTUKICAgICAgICAgICJtYXJvb24iLCMxNgogICAgICAgICAicGFsZWdyZWVuMiIsIzE3CiAgICAgICAgICAiIzMzMzMzMyIsIzE4CiAgICAgICAgICAiI0NBQjJENiIsICMxOSBsdCBwdXJwbGUKICAgICAgICAgICIjRkRCRjZGIiwgIzIwIGx0IG9yYW5nZQogICAgICAgICAia2hha2kyIiwjMjEKICAgICAgICAgInNreWJsdWUyIiwjMjIKICAgICAgICAgInN0ZWVsYmx1ZTQiLCMyMwogICAgICAgICAiZ3JlZW4xIiwjMjQKICAgICAgICAgInllbGxvdzQiLCMyNQogICAgICAgICAieWVsbG93MyIsIzI2CiAgICAgICAgICIjRkI5QTk5IiwgIzI3IGx0IHBpbmsKICAgICAgICAgImJyb3duIiwjMjgKICAgICAgICAgIiMwMDAwOTkiLCMyOQogICAgICAgICAiI0NDMzMwMCIjMzAKICAgICAgICAgKQogCnBpZShyZXAoMSwzMCksIGNvbD1jMzApCmBgYApDaG9vc2luZyBmb3IgdGhlIHNhbXBsZXMKCmBgYHtyfQpjX3NhbXBsZV9jb2wgPC0gYzMwW2MoMywyMiwxOSwzMCldCmBgYAoKQ2hvb3NpbmcgZm9yIHRoZSBjbHVzdGVycwpgYGB7cn0KY19jbHVzdF9jb2wgPC0gYzMwW2MoMSwyLDMsNCw1LDYsNyw4LDExLDE0LDIyLDI0KV0KYGBgCgpfXyBUaGlzIGRhdGEgc2V0IHdhcyBub3Qgbm9ybWFsaXplZCB1c2luZyAxMFggZG93bnNhbXBsaW5nIGFzIHRoZSBzYW1wbGVzIGhhdmUgbGFyZ2UgdmFyaWFiaWxpdHkgaW4gdGhlaXIgY2VsbCBudW1iZXJzLl9fCgojIyBJbXBvcnRpbmcgY2VsbC1yYW5nZXIgZGF0YQpgYGB7cn0KY2VsbHJhbmdlcl9waXBlc3RhbmNlX3BhdGggPC0gImZpbHRlcmVkX2ZlYXR1cmVfYmNfbWF0cml4X3dvcmtzaG9wLyIKcHJvamVjdERhdGE8LSBSZWFkMTBYKGNlbGxyYW5nZXJfcGlwZXN0YW5jZV9wYXRoKQojYW5hbHlzaXNfcmVzdWx0cyA8LSBsb2FkX2NlbGxyYW5nZXJfYW5hbHlzaXNfcmVzdWx0cyhjZWxscmFuZ2VyX3BpcGVzdGFuY2VfcGF0aCkKYGBgCgoKCiMjIEV4dHJhY3RpbmcgY2VsbCBpbmZvcm1hdGlvbgpgYGB7cn0KIyMgU2FtcGxlIGluZm9ybWF0aW9uLiAgRURJVFMgUkVRVUlSRUQKCiMjIE9uZSBzYW1wbGUKI1NhbXBsZTFfYmFyY29kZXMgIDwtIGRhdGEuZnJhbWUoYmFyY29kZT1jb2xuYW1lcyhwcm9qZWN0X2RhdGEpLCBTYW1wbGU9IkV2YV9TMSIpCgojIyBUd28gKG9yIG1vcmUpIHNhbXBsZXMKU2FtcGxlMV9iYXJjb2RlcyAgPC0gZGF0YS5mcmFtZShiYXJjb2RlPWNvbG5hbWVzKHByb2plY3REYXRhKVtncmVwKCIxIiwgY29sbmFtZXMocHJvamVjdERhdGEpKV0sIFNhbXBsZT0iUGF0aWVudDEiKQpTYW1wbGUyX2JhcmNvZGVzICA8LSBkYXRhLmZyYW1lKGJhcmNvZGU9Y29sbmFtZXMocHJvamVjdERhdGEpW2dyZXAoIjIiLCBjb2xuYW1lcyhwcm9qZWN0RGF0YSkpXSwgU2FtcGxlPSJQYXRpZW50MiIpClNhbXBsZTNfYmFyY29kZXMgIDwtIGRhdGEuZnJhbWUoYmFyY29kZT1jb2xuYW1lcyhwcm9qZWN0RGF0YSlbZ3JlcCgiMyIsIGNvbG5hbWVzKHByb2plY3REYXRhKSldLCBTYW1wbGU9IlBhdGllbnQzIikKU2FtcGxlNF9iYXJjb2RlcyAgPC0gZGF0YS5mcmFtZShiYXJjb2RlPWNvbG5hbWVzKHByb2plY3REYXRhKVtncmVwKCI0IiwgY29sbmFtZXMocHJvamVjdERhdGEpKV0sIFNhbXBsZT0iUGF0aWVudDQiKQoKcHJpbnQocGFzdGUwKCdOdW1iZXIgb2YgU2FtcGxlMSBjZWxsczogJyxkaW0oU2FtcGxlMV9iYXJjb2RlcylbMV0pKQpwcmludChwYXN0ZTAoJ051bWJlciBvZiBTYW1wbGUyIGNlbGxzOiAnLGRpbShTYW1wbGUyX2JhcmNvZGVzKVsxXSkpCnByaW50KHBhc3RlMCgnTnVtYmVyIG9mIFNhbXBsZTMgY2VsbHM6ICcsZGltKFNhbXBsZTNfYmFyY29kZXMpWzFdKSkKcHJpbnQocGFzdGUwKCdOdW1iZXIgb2YgU2FtcGxlNCBjZWxsczogJyxkaW0oU2FtcGxlNF9iYXJjb2RlcylbMV0pKQpgYGAKCgpfX0NvbmZpcm1lZCB0aGlzIG51bWJlciB3aXRoIHRoZSBRQyBvdXRwdXQgcHJvZHVjZWQgYnkgY2VsbCByYW5nZXIuX18KCgpgYGB7cn0KI0JhcmNvZGVzIAojRm9yIHNpbmdsZSBzYW1wbGUKI2Fubm90QmFyY29kZSA8LSBTYW1wbGUxX2JhcmNvZGVzCiNyb3duYW1lcyhhbm5vdEJhcmNvZGUpIDwtIGFubm90QmFyY29kZSRiYXJjb2RlCiNoZWFkKGFubm90QmFyY29kZSkKCiMjIEZvciBtdWx0aXBsZSBzYW1wbGVzCmFubm90QmFyY29kZSA8LSByYmluZChTYW1wbGUxX2JhcmNvZGVzLCBTYW1wbGUyX2JhcmNvZGVzLCBTYW1wbGUzX2JhcmNvZGVzLCBTYW1wbGU0X2JhcmNvZGVzKQpyb3duYW1lcyhhbm5vdEJhcmNvZGUpIDwtIGFubm90QmFyY29kZSRiYXJjb2RlCmhlYWQoYW5ub3RCYXJjb2RlKQpgYGAKCiMjIyBDcmVhdGluZyBgU2luZ2xlQ2VsbEV4cGVyaW1lbnRgIG9iamVjdAoKQ3JlYXRpbmcgdGhlIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgb2JqZWN0IHVzaW5nIGBzY2F0ZXJgLiBXZSB3b3VsZCBiZSB1c2luZyB0aGUgYGV4cHJzYCBmdW5jdGlvbiBvZiBgZ2JtYC4gTGF0ZXIgb24gc2NhdGVyIG1pZ2h0IGFkZCBpdHMgb3duIGZ1bmN0aW9uIHRvIGlucHV0IDEwWCBkYXRhLCBidXQgYmVmb3JlIHRoYXQgd2Ugd2lsbCBiZSB1c2luZyB0aGlzIG1ldGhvZApgYGB7cn0KY2RTYyA8LSBTaW5nbGVDZWxsRXhwZXJpbWVudChhc3NheXMgPSBsaXN0KGNvdW50cyA9IGFzLm1hdHJpeChwcm9qZWN0RGF0YSkpKQpjb2xEYXRhKGNkU2MpJGJhcmNvZGUgPC0gYW5ub3RCYXJjb2RlJGJhcmNvZGUKY29sRGF0YShjZFNjKSRTYW1wbGUgPC0gYW5ub3RCYXJjb2RlJFNhbXBsZQpyb3dEYXRhKGNkU2MpJHN5bWJvbCA8LSByb3duYW1lcyhwcm9qZWN0RGF0YSkKY2RTYyA8LSBzY2F0ZXI6OmNhbGN1bGF0ZVFDTWV0cmljcyhjZFNjKQpgYGAKYGBge3J9CmNkU2MKYGBgCgojIyMjIFZpZXdpbmcgaW5mb3JtYXRpb24gcmVsYXRlZCB3aXRoIGNlbGxzCldlIHdpbGwgdXNlIGBjb2xEYXRhKClgIGZ1bmN0aW9uIHRvIGFjY2VzcyB0aGUgbWV0YWRhdGEgcmVsYXRlZCB3aXRoIGNlbGxzCgpgYGB7cn0KbmFtZXMoY29sRGF0YShjZFNjKSkKYGBgCgoKIyMjIyBWaWV3aW5nIFFDIGluZm9ybWF0aW9uIHJlbGF0ZWQgd2l0aCBnZW5lcwpXZSB1c2UgYHJvd0RhdGEoKWAgZnVuY3Rpb24gdG8gYWNjZXNzIHRoZSBtZXRhZGF0YSByZWxhdGVkIHdpdGggZ2VuZXMKYGBge3J9Cm5hbWVzKHJvd0RhdGEoY2RTYykpCmBgYAoKCiMjIyMgTm9ybWFsaXphdGlvbgoKSGVyZSB3ZSB1c2UgbG9nMi1jb3VudHMtcGVyLW1pbGxpb24gd2l0aCBhbiBvZmZzZXQgb2YgMSBhcyB0aGUgZXhwcnMgdmFsdWVzLiBXZSBoYXZlIHRvIG5vdGUgdGhhdCB0aGUgQ1BNIGZvciBzY2F0ZXIgaXMgZGlmZmVyZW50IHRoYW4gdGhlIHJlZ3VsYXIgQ1BNIGNhbGN1bGF0aW9uIGFzIHdlIGFyZSBjb25zaWRlcmluZyB0aGUgc3RlcC1zaXplIGhlcmUuIAoKVGhlIHZhbHVlIG9mIHRoZSBsb2ctQ1BNcyBpcyBleHBsYWluZWQgYnkgYWRkaW5nIGEgcHJpb3IgY291bnQgdG8gYXZvaWQgdW5kZWZpbmVkIHZhbHVlcyBhZnRlciB0aGUgbG9nLXRyYW5zZm9ybWF0aW9uLCBtdWx0aXBseWluZyBieSBhIG1pbGxpb24sIGFuZCBkaXZpZGluZyBieSB0aGUgbWVhbiBsaWJyYXJ5IHNpemUuIFRoaXMgc2l6ZSBmYWN0b3JzIGFyZSB1c2VkIHRvIGRlZmluZSB0aGUgZWZmZWN0aXZlIGxpYnJhcnkgc2l6ZXMuIFRoaXMgaXMgZG9uZSBieSBzY2FsaW5nIGFsbCBzaXplIGZhY3RvcnMgc3VjaCB0aGF0IHRoZSBtZWFuIHNjYWxlZCBzaXplIGZhY3RvciBpcyBlcXVhbCB0byB0aGUgbWVhbiBzdW0gb2YgY291bnRzIGFjcm9zcyBhbGwgZmVhdHVyZXMuIFRoZSBlZmZlY3RpdmUgbGlicmFyeSBzaXplcyBhcmUgdGhlbiB1c2VkIHRvIGluIHRoZSBkZW5vbWluYXRvciBvZiB0aGUgQ1BNIGNhbGN1bGF0aW9uLiBUaGUgd2F5IHRoYXQgYHNjYXRlcmAgY2FsY3VsYXRlcyB0aGUgbG9nLW5vcm1hbGl6ZWQgY291bnRzIGFyZQoKYGBgCmxpYi5zaXplcyA8LSBjb2xTdW1zKGNvdW50cyhleGFtcGxlX3NjZXNldCkpCmxpYi5zaXplcyA8LSBsaWIuc2l6ZXMvbWVhbihsaWIuc2l6ZXMpCmxvZzIoY291bnRzKGV4YW1wbGVfc2Nlc2V0KVsxLF0vbGliLnNpemVzKzEpCmBgYApXZSBub3cgbm9ybWFsaXplIGZvciBhbGwgdGhlIGNvdW50cwpgYGB7cn0KZXhwcnMoY2RTYykgPC0gbG9nMihzY2F0ZXI6OmNhbGN1bGF0ZUNQTShjZFNjLCB1c2Vfc2l6ZV9mYWN0b3JzID0gVFJVRSkgKyAxKQpgYGAKClNhdmluZyBEYXRhc2V0CmBgYHtyfQpzYXZlLmltYWdlKCkKIyBzYXZlLmltYWdlKCdQQ18yMDE4MTAwMi5SRGF0YScpCmBgYAoKIyMgUUMgYW5hbHlzaXMKQXQgdGhlIHN0YXJ0IG9mIHRoZSBRQyBhbmFseXNpcyB3ZSBtYWtlIGRpZmZlcmVudCBwbG90cyB0byB2aXN1YWxpemUgdGhlIHN1bW1hcnkgc3RhdGlzdGljcy4KCiMjIyBDZWxsIFFDCkxvdy1xdWFsaXR5IGNlbGxzIG5lZWQgdG8gYmUgaWRlbnRpZmllZCBhbmQgcmVtb3ZlZCB0byBlbnN1cmUgdGhhdCB0aGUgdGVjaG5pY2FsIGVmZmVjdHMgZG8gbm90IGRpc3RvcnQgZG93bnN0cmVhbSBhbmFseXNpcyByZXN1bHRzLiBUaHJlZSBjb21tb24gbWVhc3VyZXMgb2YgY2VsbCBxdWFsaXR5IGFyZToKCiogVGhlIHRvdGFsIGNvdW50cyBvciBfbGlicmFyeSBzaXplXyBwZXIgY2VsbAoqIFRoZSBfbnVtYmVyIG9mIGV4cHJlc3NlZCBnZW5lc18gaW4gZWFjaCBjZWxsIGxpYnJhcnkKKiBQcm9wb3J0aW9uIG9mIF9taXRvY2hvbmRyaWFsIGdlbmVzXyBpbiBlYWNoIGNlbGwgbGlicmFyeQoKClRoZSBfbGlicmFyeSBzaXplXyBpcyBkZWZpbmVkIGFzIHRoZSB0b3RhbCBzdW0gb2YgY291bnRzIGFjcm9zcyBhbGwgZ2VuZXMuIENlbGxzIHdpdGggcmVsYXRpdmVseSBzbWFsbCBsaWJyYXJ5IHNpemVzIGFyZSBjb25zaWRlcmVkIHRvIGJlIG9mIGxvdyBxdWFsaXR5IGFzIHRoZSBSTkEgaGFzIG5vdCBiZWVuIGVmZmljaWVudGx5IGNhcHR1cmVkIChpLmUuLCBjb252ZXJ0ZWQgaW50byBjRE5BIGFuZCBhbXBsaWZpZWQpIGR1cmluZyBsaWJyYXJ5IHByZXBhcmF0aW9uLgoKVGhlIF9udW1iZXIgb2YgZXhwcmVzc2VkIGdlbmVzXyBpbiBlYWNoIGNlbGwgaXMgZGVmaW5lZCBhcyB0aGUgbnVtYmVyIG9mIGdlbmVzIHdpdGggbm9uLXplcm8gY291bnRzIGZvciB0aGF0IGNlbGwuIEFueSBjZWxsIHdpdGggdmVyeSBmZXcgZXhwcmVzc2VkIGdlbmVzIGlzIGxpa2VseSB0byBiZSBvZiBwb29yIHF1YWxpdHkgYXMgdGhlIGRpdmVyc2UgdHJhbnNjcmlwdCBwb3B1bGF0aW9uIGhhcyBub3QgYmVlbiBzdWNjZXNzZnVsbHkgY2FwdHVyZWQuCgpIaWdoIHByb3BvcnRpb25zIG9mIHJlYWRzIG1hcHBpbmcgdG8gX21pdG9jaG9uZHJpYWwgZ2VuZXNfIGFyZSBpbmRpY2F0aXZlIG9mIHBvb3ItcXVhbGl0eSBjZWxscyAoW0lsaWNpYyBldCBhbC4gMjAxNl0oaHR0cHM6Ly9nZW5vbWViaW9sb2d5LmJpb21lZGNlbnRyYWwuY29tL2FydGljbGVzLzEwLjExODYvczEzMDU5LTAxNi0wODg4LTEpOyBbSXNsYW0gZXQgYWwsIDIwMTRdKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvbm1ldGguMjc3MikpLCBwb3NzaWJseSBiZWNhdXNlIG9mIGluY3JlYXNlZCBhcG9wdG9zaXMgYW5kL29yIGxvc3Mgb2YgY3l0b3BsYXNtaWMgUk5BIGZyb20gbHlzZWQgY2VsbHMuCgoKCiMjIyMgUmVhZHMgbWFwcGluZyB3aXRoIG1pdG9jaG9uZHJpYWwgcmVhZHMKV2UgaWRlbnRpZnkgdGhlIHJvd3MgY29ycmVzcG9uZGluZyB0byBtaXRvY2hvbmRyaWFsIGdlbmVzLgoKYGBge3J9CiMjIEh1bWFuCm15LmlkcyA8LSBnc3ViKCdcXC4uKicsJycsdG9sb3dlcihyb3duYW1lcyhjZFNjKSkpCmVuc2VtYmwgPSB1c2VFbnNlbWJsKGJpb21hcnQ9ImVuc2VtYmwiLCBkYXRhc2V0PSJoc2FwaWVuc19nZW5lX2Vuc2VtYmwiLCBtaXJyb3IgPSAidXNlYXN0IikKI2Vuc2VtYmwgPSB1c2VFbnNlbWJsKGJpb21hcnQ9ImVuc2VtYmwiLCBkYXRhc2V0PSJoc2FwaWVuc19nZW5lX2Vuc2VtYmwiLCBob3N0ID0gInVzZWFzdC5lbnNlbWJsLm9yZyIpCmNock5hbWUgPC0gZ2V0Qk0oYXR0cmlidXRlcz1jKCdlbnNlbWJsX2dlbmVfaWQnLCdoZ25jX3N5bWJvbCcsJ2Nocm9tb3NvbWVfbmFtZScpLCBmaWx0ZXJzID0gJ2hnbmNfc3ltYm9sJywgdmFsdWVzID1teS5pZHMsIG1hcnQgPSBlbnNlbWJsKQoKaXMubWl0byA8LSBjaHJOYW1lJGNocm9tb3NvbWVfbmFtZSA9PSAiTVQiICYgIWlzLm5hKGNock5hbWUkY2hyb21vc29tZV9uYW1lKQpzdW0oaXMubWl0bykKCiMjIE1vdXNlCiNpcy5taXRvIDwtIGdyZXBsKCJebXQtIiwgcm93RGF0YShjZFNjKSRzeW1ib2wpCiNjZFNjIDwtIGNhbGN1bGF0ZVFDTWV0cmljcyhjZFNjLCBmZWF0dXJlX2NvbnRyb2xzPWxpc3QoTXQ9aXMubWl0bykpCiNwcmludChwYXN0ZTAoJ051bWJlciBvZiBhbm5vdGF0ZWQgbWl0b2Nob25kcmlhbCBnZW5lcyA9ICcsYXMuY2hhcmFjdGVyKHN1bShpcy5taXRvKSkpKQoKYGBgCgoKYGBge3J9CmNkU2MgPC0gY2FsY3VsYXRlUUNNZXRyaWNzKGNkU2MsIGZlYXR1cmVfY29udHJvbHM9bGlzdChNdD1pcy5taXRvKSkKYGBgCgpQbG90dGluZyB0aGUgaGlzdG9ncmFtcyBvZiBteSBRQyBlbnRyaWVzLgpgYGB7ciBmaXRRQ19NdCwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTB9CnBhcihtZnJvdz1jKDIsMikpCmhpc3QoKGNkU2MkdG90YWxfY291bnRzKS8xZTYsIHhsYWI9IkxpYnJhcnkgc2l6ZXMgKG1pbGxpb25zKSIsIG1haW49Ikhpc3RvZ3JhbSBvZiBMaWJyYXJ5IHNpemUiLAogICAgIGJyZWFrcz0xMDAsIGNvbD0iZ3JleTgwIiwgeWxhYj0iTnVtYmVyIG9mIGNlbGxzIikKCmhpc3QoY2RTYyR0b3RhbF9mZWF0dXJlc19ieV9jb3VudHMsIHhsYWI9Ik51bWJlciBvZiBleHByZXNzZWQgZ2VuZXMiLCBtYWluPSJIaXN0b2dyYW0gb2YgTm8uIG9mIEZlYXR1cmVzIiwKICAgICBicmVha3M9MTAwLCBjb2w9ImdyZXk4MCIsIHlsYWI9Ik51bWJlciBvZiBjZWxscyIpCgpoaXN0KGNkU2MkcGN0X2NvdW50c19NdCwgeGxhYj0iTWl0b2Nob25kcmlhbCBwcm9wb3J0aW9uICglKSIsCiAgICAgeWxhYj0iTnVtYmVyIG9mIGNlbGxzIiwgYnJlYWtzPTEwMCwgbWFpbj0iSGlzdG9ncmFtIG9mIE1pY3RvY2hvbmRyaWEgJSIsIGNvbD0iZ3JleTgwIikKYGBgCgpUaGUgc3VtbWFyeSBzdGF0aXN0aWNzIGZvciBtaXRvY2hvbmRyaWFsIHJlYWQgcHJvcG9ydGlvbiBpcyByZWFzb25hYmx5IGdvb2QuIEFzIGFueXRoaW5nIGJlbG93IDEwJSBpcyB2ZXJ5IGdvb2QgYW5kIGhlcmUgdGhlIG1lYW4gaXMgYXJvdW5kIDclLiBIb3dldmVyLCB0aGVyZSBhcmUgc29tZSBvdXRsaWVyIGNlbGxzIGV4cHJlc3NpbmcgdmVyeSBoaWdoIG1pdG9jb25kcmlhbCBnZW5lcy4gSSBhbSBnb2luZyB0byBmaWx0ZXIgdGhvc2UgZ2VuZXMgb3V0LgoKYGBge3J9CnN1bW1hcnkoY2RTYyRwY3RfY291bnRzX010KQpgYGAKCl9fIE1JVE9DSE9ORFJJQUwgR0VORVMgUUMgTk9URVMgX18KCiogQSBtZWFuIGJlbG93IDEwJSBpcyB2ZXJ5IGdvb2QuCiogQSBtZWFuIGFib3ZlIDI1JSBpcyBubyBnb29kLgoKCl9fTUlUT0NIT05EUklBTCBHRU5FUyBRQyBOT1RFUyBGT1IgVEhJUyBEQVRBU0VUX18KCiogVGhlIHN1bW1hcnkgc3RhdGlzdGljcyBmb3IgbWl0b2Nob25kcmlhbCByZWFkIHByb3BvcnRpb24gaXMgdmVyeSBnb29kLgoqIEhvd2V2ZXIsIHRoZXJlIGFyZSBzb21lIG91dGxpZXIgY2VsbHMgZXhwcmVzc2luZyB2ZXJ5IGhpZ2ggbWl0b2NvbmRyaWFsIGdlbmVzLiBUaGVzZSB3aWxsIGJlIGZpbHRlcmVkIG91dC4KCkl0IGlzIGFsc28gdmFsdWFibGUgdG8gZXhhbWluZSBob3cgdGhlIFFDIG1ldHJpY3MgYmVoYXZlIHdpdGggcmVzcGVjdCB0byBlYWNoIG90aGVyLiBHZW5lcmFsbHksIHRoZXkgd2lsbCBiZSBpbiByb3VnaCBhZ3JlZW1lbnQsIGkuZS4sIGNlbGxzIHdpdGggbG93IHRvdGFsIGNvdW50cyB3aWxsIGFsc28gaGF2ZSBsb3cgbnVtYmVycyBvZiBleHByZXNzZWQgZmVhdHVyZXMgYW5kIGhpZ2ggbWl0b2Nob25kcmlhbCBwcm9wb3J0aW9ucy4KCmBgYHtyfQpwYXIobWZyb3c9YygxLDMpKQpwbG90KGNkU2MkdG90YWxfZmVhdHVyZXNfYnlfY291bnRzLCBjZFNjJHRvdGFsX2NvdW50cy8xZTYsIHhsYWI9Ik51bWJlciBvZiBleHByZXNzZWQgZ2VuZXMiLAogICAgeWxhYj0iTGlicmFyeSBzaXplIChtaWxsaW9ucykiKQpwbG90KGNkU2MkdG90YWxfZmVhdHVyZXNfYnlfY291bnRzLCBjZFNjJHBjdF9jb3VudHNfTXQsIHhsYWI9Ik51bWJlciBvZiBleHByZXNzZWQgZ2VuZXMiLAogICAgeWxhYj0iTWl0b2Nob25kcmlhbCBwcm9wb3J0aW9uICglKSIpCnBsb3QoY2RTYyR0b3RhbF9jb3VudHMvMWU2LCBjZFNjJHBjdF9jb3VudHNfTXQsIHhsYWI9IlRvdGFsIGNvdW50cyAoaW4gbWlsbGlvbnMpIiwKICAgIHlsYWI9Ik1pdG9jaG9uZHJpYWwgcHJvcG9ydGlvbiAoJSkiKQoKYGBgCgoKYGBge3J9CnBsb3RSb3dEYXRhKGNkU2MsIHggPSAibl9jZWxsc19ieV9jb3VudHMiLCB5ID0gImxvZzEwX3RvdGFsX2NvdW50cyIsIGNvbG91cl9ieSA9ICJpc19mZWF0dXJlX2NvbnRyb2xfTXQiKQpgYGAKX19UaGlzIHBsb3RfXyBzaG93cyBsb2cxMCB0b3RhbCBjb3VudHMgcGVyIGdlbmUgKHktYXhpcykgdmVyc3VzIG51bWJlciBvZiBjZWxscyBleHByZXNzaW5nIHRoYXQgZ2VuZSAoeC1heGlzKS4gR2VuZXJhbGx5LCBpbiBzaW5nbGUgY2VsbCBkYXRhc2V0cyB0aGVyZSBhcmUgc29tZSBnZW5lcyB3aXRoIHZlcnkgbG93LCBvciB2ZXJ5IGhpZ2gsIHRvdGFsIGNvdW50cywgd2hpY2ggYWNjb3VudHMgZm9yIHRoZSBTIHNoYXBlIG9mIHRoZSBwbG90LgoKCkluIHRoZSBmaWd1cmUgYWJvdmUgd2Ugc2VlIHRoYXQgc29tZSBvZiB0aGUgbWl0b2Nob25kcmlhbCBnZW5lcyAobWFya2VkIGluIG9yYW5nZSkgYXQgdGhlIHRvcCByaWdodCBjb3JuZXIgb2YgdGhlIGZpZ3VyZSBoYXMgdmVyeSBoaWdoIGxvZzEwX3RvdGFsX2NvdW50cyBhbmQgYWxzbyBhcmUgZXhwcmVzc2VkIGluIGEgbGFyZ2UgbnVtYmVyIG9mIGNlbGxzIChhbG1vc3QgaW4gYWxsIGNlbGxzKS4gVGhpcyBpcyBub3QgdmVyeSBzdXJwcmlzaW5nIGFzIHNvbWUgb2YgdGhlIG1pdG9jaG9uZHJpYWwgZ2VuZXMgYXJlIGluZGVlZCBleHByZXNzZWQgYWNyb3NzIGFsbCB0aGUgY2VsbHMgYXMgdGhleSBhcmUgcHJvZHVjaW5nIGVuZXJneS4gVGhlIHdvcnJ5aW5nIHBhcnQgd291bGQgYmUgaWYgdGhlc2UgZ2VuZXMgdGFrZXMgbWFqb3JpdHkgb2YgYSBjZWxsJ3MgcmVhZCAobW9yZSB0aGFuIDgwJSkuIFRoZW4gSSB3b3VsZCByZW1vdmUgdGhvc2UgY2VsbHMuCgpgYGB7cn0KcGxvdFJvd0RhdGEoY2RTYywgeCA9ICJuX2NlbGxzX2J5X2NvdW50cyIsIHkgPSAibG9nMTBfdG90YWxfY291bnRzIiwgY29sb3VyX2J5ID0gInBjdF9kcm9wb3V0X2J5X2NvdW50cyIpCmBgYAoKX19Ecm9wb3V0c19fIGlzIGEgdGVybSBmb3IgZ2VuZXMgd2l0aCBubyByZWFkIGNvdW50cy4gQXMgZXhwZWN0ZWQsIGNlbGxzIHdpdGggbG93IG51bWJlciBvZiByZWFkcyBoYXZlIGhpZ2hlciBkcm9wb3V0IGNvdW50cy4KCmBgYHtyfQpwbG90Q29sRGF0YShjZFNjLCB4ID0gImxvZzEwX3RvdGFsX2NvdW50cyIsIHkgPSAibG9nMTBfdG90YWxfZmVhdHVyZXNfYnlfY291bnRzIiwgY29sb3VyX2J5ID0gInBjdF9jb3VudHNfTXQiKQpgYGAKQ2VsbHMgb24gdGhlIGxpbmVhciBwYXR0ZXJuIHNob3cgYSBnb29kIGNvcnJlbGF0aW9uIGJldHdlZW4gdG90YWwgY291bnRzIGFuZCB0aGUgbnVtYmVyIG9mIGdlbmUgZmVhdHVyZXMgd2l0aCBjb3VudHMuIFByb2JsZW1hdGljIGNlbGxzIGRldmlhdGUgZnJvbSB0aGUgbGluZWFyIHRyZW5kLgoKQWdhaW4gY2VsbHMgd2l0aCBsb3dlciByZWFkIGNvdW50cyBhbmQgbG93ZXIgdG90YWwgZmVhdHVyZSBjb3VudHMgaGF2ZSB2ZXJ5IGhpZ2ggcHJvcG9ydGlvbiBvZiBtaXRjaG9uZHJpYWwgcmVhZHMsIGNsZWFybHkgaW5kaWNhdGluZyB0aGF0IHRoZXNlIGNlbGxzIGVpdGhlciBzdGFydGVkIGFwb3B0b3NpcyBvciBicm9rZW4gYWJydXB0bHkuIFdlIGNhbiBzYWZlbHkgcmVtb3ZlIHRoZXNlIGNlbGxzIGluIG91ciBRQyBmaWx0ZXJpbmcuIAoKCmBgYHtyfQpwbG90Q29sRGF0YShjZFNjLCB4ID0gImxvZzEwX3RvdGFsX2ZlYXR1cmVzX2J5X2NvdW50cyIsIHk9ImxvZzEwX3RvdGFsX2NvdW50cyIsIHNpemVfYnkgPSJ0b3RhbF9jb3VudHNfTXQiLCBjb2xvdXJfYnkgID0gInBjdF9jb3VudHNfTXQiKQpgYGAKClRoZSBmb2xsb3dpbmcgZmlndXJlIGdpdmVzIGFuIGlkZWEgb2YgaG93IG1hbnkgZ2VuZXMgYXJlIGV4cHJlc3NlZCBpbiBob3cgbWFueSBjZWxscy4KCmBgYHtyfQpwbG90RXhwcnNGcmVxVnNNZWFuKGNkU2MpCmBgYAojIDMuIFF1YWxpdHkgZmlsdGVyaW5nIG9mIGNlbGxzCgpfX1BpY2tpbmcgdGhyZXNob2xkcyBmb3IgZmlsdGVyaW5nIG91dCBwb29yIGNlbGxzX18gaXMgbm90IHN0cmFpZ2h0Zm9yd2FyZCBmb3IgZGlmZmVyZW50IG1ldHJpY3MgYXMgdGhlaXIgYWJzb2x1dGUgdmFsdWVzIGRlcGVuZCBvbiB0aGUgcHJvdG9jb2wgYW5kIGJpb2xvZ2ljYWwgc3lzdGVtLiBGb3IgZXhhbXBsZSwgc2VxdWVuY2luZyB0byBncmVhdGVyIGRlcHRoIHdpbGwgbGVhZCB0byBtb3JlIHJlYWRzLCByZWdhcmRsZXNzIG9mIHRoZSBxdWFsaXR5IG9mIHRoZSBjZWxscy4gVG8gb2J0YWluIGFuIGFkYXB0aXZlIHRocmVzaG9sZCwgdGhlIGFzc3VtcHRpb24gbWFkZSBoZXJlIGlzIHRoYXQgbW9zdCBvZiB0aGUgZGF0YXNldCBjb25zaXN0cyBvZiBoaWdoLXF1YWxpdHkgY2VsbHMuIFBsb3RzIHRvIGZhY2lsaXRhdGUgcGlja2luZyB0aHJlc2hvbGRzIGZvciBjZWxsIGN1dG9mZnMgYXJlIGJlbG93LgoKYGBge3J9CmN1dF9vZmZfcmVhZHMgPC0gbWVkaWFuKGNkU2MkbG9nMTBfdG90YWxfY291bnRzKSAtIDMqbWFkKGNkU2MkbG9nMTBfdG90YWxfY291bnRzKQpkZiA8LSBkYXRhLmZyYW1lKHg9Y2RTYyRsb2cxMF90b3RhbF9jb3VudHMsIFNhbXBsZSA9IGNkU2MkU2FtcGxlKQpwbG90X3JlYWRzIDwtIGdncGxvdChkZiwKICAgICAgIGFlcyh4ID0geCwgZmlsbCA9IGFzLmZhY3RvcihTYW1wbGUpKSkgKyAKICAgICAgIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuNSkgKwogICAgICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gY3V0X29mZl9yZWFkcywgY29sb3VyPSJncmV5IiwgbGluZXR5cGUgPSAibG9uZ2Rhc2giKSArCiAgICAgICBsYWJzKHggPSBleHByZXNzaW9uKCdsb2cnWzEwXSonKExpYnJhcnkgU2l6ZSknKSwgdGl0bGUgPSAiVG90YWwgcmVhZHMgZGVuc2l0eSIsIGZpbGwgPSAiU2FtcGxlIikgKyAKICAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpKwogICAgICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWNfc2FtcGxlX2NvbCkKCgpjdXRfb2ZmX21STkEgPC0gbWVkaWFuKGNkU2MkbG9nMTBfdG90YWxfZmVhdHVyZXNfYnlfY291bnRzKSAtIDMqbWFkKGNkU2MkbG9nMTBfdG90YWxfZmVhdHVyZXNfYnlfY291bnRzKQpkZiA8LSBkYXRhLmZyYW1lKHg9Y2RTYyRsb2cxMF90b3RhbF9mZWF0dXJlc19ieV9jb3VudHMsIFNhbXBsZSA9IGNkU2MkU2FtcGxlKQpwbG90X21STkEgPC0gZ2dwbG90KGRmLAogICAgICAgYWVzKHggPSB4LCBmaWxsID0gYXMuZmFjdG9yKFNhbXBsZSkpKSArIAogICAgICAgZ2VvbV9kZW5zaXR5KGFscGhhID0gMC41KSArCiAgICAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBjdXRfb2ZmX21STkEsIGNvbG91cj0iZ3JleSIsIGxpbmV0eXBlID0gImxvbmdkYXNoIikgKwogICAgICAgbGFicyh4ID0gZXhwcmVzc2lvbignbG9nJ1sxMF0qJyhOdW1iZXIgb2YgZXhwcmVzc2VkIGdlbmVzKScpLCB0aXRsZSA9ICJUb3RhbCBnZW5lcyBleHByZXNzZWQiLCBmaWxsID0gIlNhbXBsZSIpICsgCiAgICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE0KSsKICAgICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jX3NhbXBsZV9jb2wpCgoKY3V0X29mZl9NVCA8LSBtZWRpYW4oY2RTYyRwY3RfY291bnRzX010KSArIDMqbWFkKGNkU2MkcGN0X2NvdW50c19NdCkKZGYgPC0gZGF0YS5mcmFtZSh4PWNkU2MkcGN0X2NvdW50c19NdCwgU2FtcGxlID0gY2RTYyRTYW1wbGUpCnBsb3RfTVQgPC0gZ2dwbG90KGRmLAogICAgICAgYWVzKHggPSB4LCBmaWxsID0gYXMuZmFjdG9yKFNhbXBsZSkpKSArIAogICAgICAgZ2VvbV9kZW5zaXR5KGFscGhhID0gMC41KSArCiAgICAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBjdXRfb2ZmX01ULCBjb2xvdXI9ImdyZXkiLCBsaW5ldHlwZSA9ICJsb25nZGFzaCIpICsKICAgICAgIGxhYnMoeCA9IGV4cHJlc3Npb24oJ1BjdCBvZiBNVCBFeHByZXNzaW9uJyksIHRpdGxlID0gIk1pdG9jaG9uZHJpYWwgRXhwcmVzc2lvbiIsIGZpbGwgPSAiU2FtcGxlIikgKyAKICAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpKwogICAgICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWNfc2FtcGxlX2NvbCkKCmBgYAoKYGBge3IgZmlnRGlzdCwgZmlnLmhlaWdodCA9IDgsIGZpZy53aWR0aCA9IDl9Cm11bHRpcGxvdChwbG90X3JlYWRzLCBwbG90X01ULCBwbG90X21STkEsICBjb2xzPTIpCmBgYAoKYGBge3IgZmlnSW5kRGlzdCwgZmlnLmhlaWdodCA9IDgsIGZpZy53aWR0aCA9IDl9CmRmIDwtIGRhdGEuZnJhbWUoeD1jZFNjJGxvZzEwX3RvdGFsX2NvdW50cykKYXNfdGliYmxlKGRmKSAlPiUKICBkcGx5cjo6bXV0YXRlKCJTYW1wbGUiID0gY2RTYyRTYW1wbGUpICU+JQogIGdncGxvdCggYWVzKHggPSB4LCBmaWxsID0gYXMuZmFjdG9yKFNhbXBsZSkpKSArIAogICAgICAgZ2VvbV9kZW5zaXR5KGFscGhhID0gMC41KSArCiAgICAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBjdXRfb2ZmX3JlYWRzLCBjb2xvdXI9ImdyZXkiLCBsaW5ldHlwZSA9ICJsb25nZGFzaCIpICsKICAgICAgIGxhYnMoeCA9IGV4cHJlc3Npb24oJ2xvZydbMTBdKicoTGlicmFyeSBTaXplKScpLCB0aXRsZSA9ICJUb3RhbCByZWFkcyBkZW5zaXR5IiwgZmlsbCA9ICJTYW1wbGUiKSArIAogICAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkrCiAgICAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9Y19zYW1wbGVfY29sKSArIGZhY2V0X3dyYXAoflNhbXBsZSwgbnJvdz0yLG5jb2w9MikKYGBgCgpfX1RoZSBkaXN0cmlidXRpb24gb2YgdGhlIHJlYWRzLCBudW1iZXIgb2YgZXhwcmVzc2VkIGdlbmVzIGFuZCB0aGUgcGVyY2VudCBvZiBNVCBleHByZXNzaW9uLl9fCgpUaGUgZG90dGVkIGxpbmUgcmVwcmVzZW50cyB0aGUgdGhyZXNob2xkIHdoaWNoIGlzIHRoZSAzIE1lZGlhbiBBYnNvbHV0ZSBEZXZpYXRpb24gKE1BRCkuIEZvciB0aGUgdG9wIHR3byBmaWd1cmVzLCBjZWxscyBvbiB0aGUgbGVmdCBvZiB0aGlzIHRocmVzaG9sZCB3b3VsZCBiZSBmaWx0ZXJlZCBvdXQgYW5kIGZvciB0aGUgYm90dG9tIGZpZ3VyZSwgY2VsbHMgb24gdGhlIHJpZ2h0IG9mIHRoaXMgZmlndXJlIHdvdWxkIGJlIGZpbHRlcmVkIG91dC4KCkNlbGxzIGFyZSByZW1vdmVkIHdpdGggbG9nLWxpYnJhcnkgc2l6ZXMgdGhhdCBhcmUgbW9yZSB0aGFuIDMgbWVkaWFuIGFic29sdXRlIGRldmlhdGlvbnMgKE1BRHMpIGJlbG93IHRoZSBtZWRpYW4gbG9nLWxpYnJhcnkgc2l6ZS4gKEEgbG9nLXRyYW5zZm9ybWF0aW9uIGltcHJvdmVzIHJlc29sdXRpb24gYXQgc21hbGwgdmFsdWVzLCBlc3BlY2lhbGx5IHdoZW4gdGhlIE1BRCBvZiB0aGUgcmF3IHZhbHVlcyBpcyBjb21wYXJhYmxlIHRvIG9yIGdyZWF0ZXIgdGhhbiB0aGUgbWVkaWFuKS4KClNpbWlsYXJseSBjZWxscyBhcmUgcmVtb3ZlZCB3aGVyZSB0aGUgbG9nLXRyYW5zZm9ybWVkIG51bWJlciBvZiBleHByZXNzZWQgZ2VuZXMgaXMgMyBNQURzIGJlbG93IHRoZSBtZWRpYW4uCgpGaW5hbGx5LCBjZWxscyBhcmUgcmVtb3ZlZCBoYXZpbmcgcGN0IG9mIE1UIGV4cHJlc3Npb24gYmVsb3cgMyBNQUQuCgpgYGB7cn0KcHJpbnQocGFzdGUwKCdSZWFkIGNvdW50IEN1dG9mZjogJywxMF5jdXRfb2ZmX3JlYWRzKSkKcHJpbnQocGFzdGUwKCdHZW5lcyBjb3VudCBDdXRvZmY6ICcsMTBeY3V0X29mZl9tUk5BKSkKcHJpbnQocGFzdGUwKCdNVCBwZXJjZW50IGNvdW50IEN1dG9mZjogJyxjdXRfb2ZmX01UKSkKYGBgCgoKCldlIG5vdyBjYWxjdWxhdGUgdGhlIGNlbGxzIGFuZCBnZW5lcyB0aGF0IGFyZSBkZWVtZWQgdG8gYmUgb3V0bGllcnMgZHVlIHRvIGZhbGxpbmcgYmVsb3cgMyBNQUQuCgpDZWxscyBoYXZpbmcgcmVhZCBjb3VudHMgYmVsb3cgMTMyNSAoQnlMaWJTaXplKSBhbmQgbnVtYmVyIG9mIGdlbmVzIGV4cHJlc3NlZCBiZWxvdyA4ODQgKEJ5RmVhdHVyZSkgd291bGQgYmUgZHJvcHBlZCBhbmQgaWYgY2VsbHMgYXJlIGhhdmluZyBtb3JlIHRoYW4gMC4xMiUgb2YgbWl0b2Nob25kcmlhbCByZWFkcywgSSB3b3VsZCBkcm9wIHRoZSBjZWxscy4KCmBgYHtyfQpsaWJzaXplLmRyb3AgPC0gaXNPdXRsaWVyKGNkU2MkdG90YWxfY291bnRzLCBubWFkcz0zLCB0eXBlPSJsb3dlciIsIGxvZz1UUlVFKQpmZWF0dXJlLmRyb3AgPC0gaXNPdXRsaWVyKGNkU2MkdG90YWxfZmVhdHVyZXNfYnlfY291bnRzLCBubWFkcz0zLCB0eXBlPSJsb3dlciIsIGxvZz1UUlVFKQptaXRvLmRyb3AgPC0gaXNPdXRsaWVyKGNkU2MkcGN0X2NvdW50c19NdCwgbm1hZHM9MywgdHlwZT0iaGlnaGVyIikKYGBgCgpCZWxvdyBhcmUgdGhlIHN0YXRzIGZvciB0aGUgY2VsbHMgdGhhdCB3b3VsZCBiZSBkcm9wcGVkCmBgYHtyfQpkYXRhLmZyYW1lKEJ5TGliU2l6ZT1zdW0obGlic2l6ZS5kcm9wKSwgQnlGZWF0dXJlPXN1bShmZWF0dXJlLmRyb3ApLCAKICAgICAgICAgICBCeU1pdG89c3VtKG1pdG8uZHJvcCkpCmBgYAoKU28sIGJhc2VkIG9uIGxpYnJhcnkgc2l6ZSwgODI1IGNlbGxzIHdvdWxkIGJlIGRyb3BwZWQgYW5kIGJhc2VkIG9uIG51bWJlciBvZiBmZWF0dXJlcyBleHByZXNzZWQsIDEyMTAgY2VsbHMgd291bGQgYmUgZHJvcHBlZCBhbmQgZm9yIE1pdG9jaG9uZHJpYWwgcHJvcG9ydGlvbiBvZiByZWFkcyA1MTcgY2VsbHMgd291bGQgYmUgZHJvcHBlZC4gUGxlYXNlIG5vdGUgdGhhdCBhbGwgdGhlc2UgY2VsbHMgYXJlIHJlbW92ZWQgZnJvbSB0aGUgdG90YWwgcG9wdWxhdGlvbiBhbmQgYWxzbyBtYW55IG9mIHRoZXNlIGNlbGxzIHdvdWxkIGJlIG92ZXJsYXBwaW5nIGFjcm9zcyBjb25kaXRpb25zLgoKCkdlbmVyYXRpbmcsIHRoZSBgc2NhdGVyYCBvYmplY3Qgd2l0aCBmaWx0ZXJlZCBwcm9maWxlLgpgYGB7cn0KY2RTY0ZpbHQgPC0gY2RTY1ssIShsaWJzaXplLmRyb3AgfCBmZWF0dXJlLmRyb3AgfCBtaXRvLmRyb3ApXQpjZFNjRmlsdCA8LSBjYWxjdWxhdGVRQ01ldHJpY3MoY2RTY0ZpbHQpCmBgYAoKCkJlZm9yZSBmaWx0ZXJpbmcgbnVtYmVyIG9mIGNlbGxzIHdlcmUsCmBgYHtyfQpwcmludChwYXN0ZTAoIkNlbGxzIGJlZm9yZSBmaWx0ZXJpbmc6ICIsZGltKGNkU2MpWzJdKSkKYGBgCgoKCkFmdGVyIGZpbHRlcmluZyByZW1haW5pbmcgY2VsbHMgYXJlOgpgYGB7cn0KcHJpbnQocGFzdGUwKCJDZWxscyByZW1haW5pbmcgYWZ0ZXIgZmlsdGVyaW5nOiAiLGRpbShjZFNjRmlsdClbMl0pKQpgYGAKCkZvciB0aGUgZm91ciBzYW1wbGVzLCBudW1iZXIgb2YgY2VsbHMgdGhhdCBhcmUgcmVtYWluaW5nIGFyZToKYGBge3J9CnByaW50KGxldmVscyhhcy5mYWN0b3IoY29sRGF0YShjZFNjKSRTYW1wbGUpKSkKcHJpbnQocGFzdGUwKCdCZWZvcmUgRmlsdGVyaW5nOiAnLCB0YWJsZShjb2xEYXRhKGNkU2MpJFNhbXBsZSkpKQpwcmludChwYXN0ZTAoJ0FmdGVyIEZpbHRlcmluZzogJywgdGFibGUoY29sRGF0YShjZFNjRmlsdCkkU2FtcGxlKSkpCmBgYAoKU28sIHRoZSBjZWxscyB0aGF0IGFyZSBiZWluZyBmaWx0ZXJlZCBvdXQgYXJlIGtpbmQgb2YgZXZlbmx5IGRpc3RyaWJ1dGVkIGFjcm9zcyB0aGUgc2FtcGxlcy4gVGhpcyBpcyBraW5kIG9mIHJlYXNzdXJpbmcgdGhhdCB0aGVyZSB3YXMgbm90IGEgcG9pbnQgZmFpbHVyZSB3aGVyZSBvbmUgc2FtcGxlIHRvdGFsbHkgZmFpbGVkLiBBZnRlciBmaWx0ZXJpbmcgd2UgaGF2ZSBpbiB0b3RhbCAxODI4IGNlbGxzIHJlbWFpbmluZyBmb3IgZG93bnN0cmVhbSBhbmFseXNpcyB3aGljaCBpcyBhIHZlcnkgcmVhc29uYWJsZSBudW1iZXIuCgpfX1FDIHBsb3RzX18gYmVsb3cgdG8gY2hlY2sgcmVzdWx0IGFuZCBkZWNpZGUgd2hldGhlciBmdXJ0aGVyIGZpbHRlcmluZyBpcyByZXF1aXJlZC4KClJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRvdGFsIGNvdW50cyBhbmQgdG90YWwgZmVhdHVyZXMgYmVmb3JlIGZpbHRlcmluZzoKYGBge3J9CnAgPC0gcGxvdENvbERhdGEoY2RTYywgeD0ibG9nMTBfdG90YWxfY291bnRzIix5PSJ0b3RhbF9mZWF0dXJlc19ieV9jb3VudHMiLCBjb2xvdXJfYnkgPSAiU2FtcGxlIikgKwogICAgICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWNfc2FtcGxlX2NvbCkKcApgYGAKCkFmdGVyIGZpbHRlcmluZzoKYGBge3J9CnAgPC0gcCA8LSBwbG90Q29sRGF0YShjZFNjRmlsdCwgeD0ibG9nMTBfdG90YWxfY291bnRzIix5PSJ0b3RhbF9mZWF0dXJlc19ieV9jb3VudHMiLCBjb2xvdXJfYnkgPSAiU2FtcGxlIikgKwogICAgICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWNfc2FtcGxlX2NvbCkKcApgYGAKV2UgY291bGQgc2VlIHRoYXQgc29tZSBvZiB0aGUgb3V0bGllciBjZWxscyBoYXZlIGJlZW4gZHJvcHBlZC4KCkJlZm9yZSBmaWx0ZXJpbmc6CmBgYHtyfQpwbG90Q29sRGF0YShjZFNjLCB4ID0gImxvZzEwX3RvdGFsX2NvdW50cyIsIHkgPSAibG9nMTBfdG90YWxfZmVhdHVyZXNfYnlfY291bnRzIiwgY29sb3VyX2J5ID0gInBjdF9jb3VudHNfTXQiKQpgYGAKCkFmdGVyIGZpbHRlcmluZzoKYGBge3J9CnBsb3RDb2xEYXRhKGNkU2NGaWx0LCB4ID0gImxvZzEwX3RvdGFsX2NvdW50cyIsIHkgPSAibG9nMTBfdG90YWxfZmVhdHVyZXNfYnlfY291bnRzIiwgY29sb3VyX2J5ID0gInBjdF9jb3VudHNfTXQiKQpgYGAKV2Ugc2VlIHRoYXQgdGhlIGdvb2QgY2VsbHMgYXJlIGtlcHQgd2hpbGUgY2VsbHMgd2l0aCB2ZXJ5IGhpZ2ggTXQgcmVhZHMgYXJlIGJlaW5nIHJlbW92ZWQuCgpfX1Zpb2xpbiBwbG90c19fIGJlbG93IHRvIGludmVzdGlnYXRlIHBvc3NpYmxlIG91dGxpZXIgY2VsbHMgd2l0aCBoaWdoIHJlYWQtZGVwdGggKGNvdWxkIGJlIGRvdWJsZXRzKS4KCmBgYHtyIGZpZ1Zpb2xpbiwgZmlnLmhlaWdodD05LCBmaWcud2lkdGggPSA5fQpkZiA8LSBkYXRhLmZyYW1lKENlbGw9Y29sbmFtZXMoY2RTY0ZpbHQpLCBDZWxsVHlwZT1jZFNjRmlsdCRTYW1wbGUsIHRvdGFsRmVhdHVyZXM9Y2RTY0ZpbHQkdG90YWxfZmVhdHVyZXNfYnlfY291bnRzLCB0b3RhbENvdW50PWNkU2NGaWx0JHRvdGFsX2NvdW50cywgUGN0VG90YWxDb3VudE10PWNkU2NGaWx0JHBjdF9jb3VudHNfTXQsIFNhbXBsZT1jZFNjRmlsdCRTYW1wbGUpCnAxIDwtIGdncGxvdChkZiwgYWVzKGZhY3RvcihDZWxsVHlwZSksdG90YWxDb3VudCxjb2xvdXI9U2FtcGxlKSkKcDEgPC0gcDEgKyBnZW9tX3Zpb2xpbigpICsgZ2VvbV9qaXR0ZXIoaGVpZ2h0ID0gMCwgd2lkdGggPSAwLjEpICsgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkgKyBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1jX3NhbXBsZV9jb2wpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X3RleHQoYW5nbGU9OTAsIGhqdXN0PTEpKQoKcDIgPC0gZ2dwbG90KGRmLCBhZXMoZmFjdG9yKENlbGxUeXBlKSxQY3RUb3RhbENvdW50TXQsY29sb3VyPVNhbXBsZSkpCnAyIDwtIHAyICsgZ2VvbV92aW9saW4oKSArIGdlb21faml0dGVyKGhlaWdodCA9IDAsIHdpZHRoID0gMC4xKSArIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpICsgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9Y19zYW1wbGVfY29sKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlPTkwLCBoanVzdD0xKSkKCnAzIDwtIGdncGxvdChkZiwgYWVzKGZhY3RvcihDZWxsVHlwZSksdG90YWxGZWF0dXJlcyxjb2xvdXI9U2FtcGxlKSkKcDMgPC0gcDMgKyBnZW9tX3Zpb2xpbigpICsgZ2VvbV9qaXR0ZXIoaGVpZ2h0ID0gMCwgd2lkdGggPSAwLjEpICsgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkgKyBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1jX3NhbXBsZV9jb2wpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X3RleHQoYW5nbGU9OTAsIGhqdXN0PTEpKQoKCm11bHRpcGxvdChwMSxwMixwMyxjb2xzID0gMikKYGBgCkl0IGNsZWFybHkgbG9va3MgbGlrZSB0aGF0IHR3byBjZWxscyBhYm92ZSB0b3RhbCBjb3VudHMgb2YgMzAwMDAgYXJlIG91dGxpZXJzLiBUaGlzIGFnYWluIHdvdWxkIGJlIHJlbW92ZWQgYXMgaXQgd291bGQgb3RoZXJ3aXNlIG1pZ2h0IGJlIG11bHRpcGxldHMKCmBgYHtyfQpjZFNjRmlsdApgYGAKPGZvbnQgY29sb3I9cmVkPk1hbnVhbCBjdXRvZmYgc2VsZWN0aW9uIHJlcXVpcmVkIGluIHRoZSBuZXh0IHN0ZXAuIENhcmVmdWwgdGhpcyBjaGFuZ2VzIGNkc0ZpbHQgcGVybWFuZW50bHkuIG1ha2UgYSB0ZW1wIGZpbGUgaWYgdW5zdXJlPC9mb250PgoKYGBge3J9CmNkU2NGaWx0IDwtIGNkU2NGaWx0WywhKGNkU2NGaWx0JHRvdGFsX2NvdW50cyA+IDIwMDAwKV0KYGBgCgpgYGB7cn0KY2RTY0ZpbHQKYGBgCgpEcmF3aW5nIHRoZSB2dWlvbGluIHBsb3QgYWdhaW4KCmBgYHtyIGZpZ1Zpb2xpbl92MiwgZmlnLmhlaWdodD05LCBmaWcud2lkdGggPSA5fQpkZiA8LSBkYXRhLmZyYW1lKENlbGw9Y29sbmFtZXMoY2RTY0ZpbHQpLCBDZWxsVHlwZT1jZFNjRmlsdCRTYW1wbGUsIHRvdGFsRmVhdHVyZXM9Y2RTY0ZpbHQkdG90YWxfZmVhdHVyZXNfYnlfY291bnRzLCB0b3RhbENvdW50PWNkU2NGaWx0JHRvdGFsX2NvdW50cywgUGN0VG90YWxDb3VudE10PWNkU2NGaWx0JHBjdF9jb3VudHNfTXQsIFNhbXBsZT1jZFNjRmlsdCRTYW1wbGUpCnAxIDwtIGdncGxvdChkZiwgYWVzKGZhY3RvcihDZWxsVHlwZSksdG90YWxDb3VudCxjb2xvdXI9U2FtcGxlKSkKcDEgPC0gcDEgKyBnZW9tX3Zpb2xpbigpICsgZ2VvbV9qaXR0ZXIoaGVpZ2h0ID0gMCwgd2lkdGggPSAwLjEpICsgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkgKyBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1jX3NhbXBsZV9jb2wpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X3RleHQoYW5nbGU9OTAsIGhqdXN0PTEpKQoKcDIgPC0gZ2dwbG90KGRmLCBhZXMoZmFjdG9yKENlbGxUeXBlKSxQY3RUb3RhbENvdW50TXQsY29sb3VyPVNhbXBsZSkpCnAyIDwtIHAyICsgZ2VvbV92aW9saW4oKSArIGdlb21faml0dGVyKGhlaWdodCA9IDAsIHdpZHRoID0gMC4xKSArIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpICsgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9Y19zYW1wbGVfY29sKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlPTkwLCBoanVzdD0xKSkKCnAzIDwtIGdncGxvdChkZiwgYWVzKGZhY3RvcihDZWxsVHlwZSksdG90YWxGZWF0dXJlcyxjb2xvdXI9U2FtcGxlKSkKcDMgPC0gcDMgKyBnZW9tX3Zpb2xpbigpICsgZ2VvbV9qaXR0ZXIoaGVpZ2h0ID0gMCwgd2lkdGggPSAwLjEpICsgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkgKyBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1jX3NhbXBsZV9jb2wpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X3RleHQoYW5nbGU9OTAsIGhqdXN0PTEpKQoKCm11bHRpcGxvdChwMSxwMixwMyxjb2xzID0gMikKYGBgCkxvb2tzIGEgZ29vZCBkaXN0cmlidXRpb24gdG8gbW92ZSBmb3J3YXJkLgoKYGBge3J9CnNhdmUuaW1hZ2UoKQpgYGAKCgojIDQuIENsYXNzaWZpY2F0aW9uIG9mIGNlbGwgY3ljbGUgcGhhc2UKClRoZSBwcmVkaWN0aW9uIG1ldGhvZCB1c2VkIGhlcmUgaXMgZGVzY3JpYmVkIGJ5IFtTY2lhbGRvbmUgZXQgYWwuICgyMDE1KV0oaHR0cDovL3d3dy5zY2llbmNlZGlyZWN0LmNvbS9zY2llbmNlL2FydGljbGUvcGlpL1MxMDQ2MjAyMzE1MzAwMDk4KSB0byBjbGFzc2lmeSBjZWxscyBpbnRvIGNlbGwgY3ljbGUgcGhhc2VzIGJhc2VkIG9uIHRoZSBnZW5lIGV4cHJlc3Npb24gZGF0YS4gVXNpbmcgYSB0cmFpbmluZyBkYXRhc2V0LCB0aGUgc2lnbiBvZiB0aGUgZGlmZmVyZW5jZSBpbiBleHByZXNzaW9uIGJldHdlZW4gdHdvIGdlbmVzIHdhcyBjb21wdXRlZCBmb3IgZWFjaCBwYWlyIG9mIGdlbmVzLiBQYWlycyB3aXRoIGNoYW5nZXMgaW4gdGhlIHNpZ24gYWNyb3NzIGNlbGwgY3ljbGUgcGhhc2VzIHdlcmUgY2hvc2VuIGFzIG1hcmtlcnMuIENlbGxzIGluIGEgdGVzdCBkYXRhc2V0IGNhbiB0aGVuIGJlIGNsYXNzaWZpZWQgaW50byB0aGUgYXBwcm9wcmlhdGUgcGhhc2UsIGJhc2VkIG9uIHdoZXRoZXIgdGhlIG9ic2VydmVkIHNpZ24gZm9yIGVhY2ggbWFya2VyIHBhaXIgaXMgY29uc2lzdGVudCB3aXRoIG9uZSBwaGFzZSBvciBhbm90aGVyLiBXZSBkbyB0aGUgY2VsbCBjeWNsZSBjbGFzc2lmaWNhdGlvbiBiZWZvcmUgZ2VuZSBmaWx0ZXJpbmcgYXMgdGhpcyBwcm92aWRlcyBtb3JlIHByZWNpc2UgY2VsbCBjeWNsZSBwaGFzZSBjbGFzc2lmaWNhdGlvbnMuIFRoaXMgYXBwcm9hY2ggaXMgaW1wbGVtZW50ZWQgaW4gdGhlIEN5Y2xvbmUgZnVuY3Rpb24gdXNpbmcgYSBwcmUtdHJhaW5lZCBzZXQgb2YgbWFya2VyIHBhaXJzIGZvciBodW1hbiBkYXRhLiBTb21lIGFkZGl0aW9uYWwgd29yayBpcyBuZWNlc3NhcnkgdG8gbWF0Y2ggdGhlIGdlbmUgc3ltYm9scyBpbiB0aGUgZGF0YSB0byB0aGUgRW5zZW1ibCBhbm5vdGF0aW9uIGluIHRoZSBwcmUtdHJhaW5lZCBtYXJrZXIgc2V0LgoKYGBge3J9CmNkU2NGaWx0QW5ub3QgPC0gY2RTY0ZpbHQKYGBgCgpgYGB7cn0KaGcucGFpcnMgPC0gcmVhZFJEUyhzeXN0ZW0uZmlsZSgiZXhkYXRhIiwgImh1bWFuX2N5Y2xlX21hcmtlcnMucmRzIiwgcGFja2FnZT0ic2NyYW4iKSkKbGlicmFyeShvcmcuSHMuZWcuZGIpCmFubm8gPC0gc2VsZWN0KG9yZy5Icy5lZy5kYiwga2V5cz1hcy5jaGFyYWN0ZXIocm93bmFtZXMoY2RTY0ZpbHRBbm5vdCkpLCBrZXl0eXBlPSJTWU1CT0wiLCBjb2x1bW49IkVOU0VNQkwiKQplbnNlbWJsIDwtIGFubm8kRU5TRU1CTFttYXRjaChhcy5jaGFyYWN0ZXIocm93bmFtZXMoY2RTY0ZpbHRBbm5vdCkpLCBhbm5vJFNZTUJPTCldCmFzc2lnbm1lbnRzIDwtIGN5Y2xvbmUoY2RTY0ZpbHRBbm5vdCwgaGcucGFpcnMsIGdlbmUubmFtZXM9ZW5zZW1ibCkKYGBgCgpgYGB7cn0KZGYgPC0gZGF0YS5mcmFtZSh4PWFzc2lnbm1lbnRzJHNjb3JlJEcxLCB5PWFzc2lnbm1lbnRzJHNjb3JlJEcyTSwgU2FtcGxlPWNvbERhdGEoY2RTY0ZpbHQpJFNhbXBsZSkKIHA8LWdncGxvdChkYXRhPWRmLCBhZXMoeD14LHk9eSxjb2xvcj1TYW1wbGUpKSArIAogICAgZ2VvbV9wb2ludChzaXplPTAuNSkrCiAgICB4bGFiKCJHMSBzY29yZSIpKwogICAgeWxhYigiRzJNIHNjb3JlIikrCiAgICB5bGltKDAsMSkrCiAgICB4bGltKDAsMSkrCiAgICBnZ3RpdGxlKCJDZWxsLWN5Y2xlIGVmZmVjdHMiKSsKICAgIHRoZW1lX2xpZ2h0KGJhc2Vfc2l6ZT0xNSkrCiAgICB0aGVtZShheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZT0xMCwgdmp1c3Q9LTIpLAogICAgICAgICAgYXhpcy50ZXh0LnggID0gZWxlbWVudF90ZXh0KCBzaXplPTEwKSwKICAgICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dCggc2l6ZT0xMCx2anVzdD0yKSwKICAgICAgICAgIGF4aXMudGV4dC55ICA9IGVsZW1lbnRfdGV4dCggc2l6ZT0xMCkpICsKICAgIHRoZW1lKHBsb3QubWFyZ2luPXVuaXQoYygxLDEsMS41LDEuMiksImNtIikpKwogICAgdGhlbWUobGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTApLCNzaXplIG9mIGxlZ2VuZAogICAgICAgICAgbGVnZW5kLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MjAsIGZhY2U9ImJvbGQiKSkgKwogICAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9Y19zYW1wbGVfY29sKSArIAogICAgI3NjYWxlX2NvbG9yX2Rpc2NyZXRlKG5hbWU9bGVnZW5kLnRpdGxlKSsKICAgIGdlb21fc2VnbWVudChhZXMoeCA9IDEvMiwgeSA9IDAsIHhlbmQ9MS8yLCB5ZW5kPTEvMiksY29sb3VyPSJibGFjayIpICsgCiAgICBnZW9tX3NlZ21lbnQoYWVzKHggPSAwLCB5ID0gMS8yLCB4ZW5kPTEvMiwgeWVuZD0xLzIpLGNvbG91cj0iYmxhY2siKSArCiAgICBnZW9tX3NlZ21lbnQoYWVzKHggPSAxLzIsIHkgPSAxLzIsIHhlbmQ9MSwgeWVuZD0xKSxjb2xvdXI9ImJsYWNrIikgKwogICAgYW5ub3RhdGUoInRleHQiLCB4PTAuMDUsIHk9MC4wNSwgbGFiZWw9IlMiLCBzaXplPTgpKwogICAgYW5ub3RhdGUoInRleHQiLCB4PTAuOTUsIHk9MC4yNSwgbGFiZWw9IkcxIiwgc2l6ZT04KSsKICAgIGFubm90YXRlKCJ0ZXh0IiwgeD0wLjI1LCB5PTAuOTUsIGxhYmVsPSJHMk0iLCBzaXplPTgpCnByaW50KHApCnNtb290aFNjYXR0ZXIoYXNzaWdubWVudHMkc2NvcmUkRzEsIGFzc2lnbm1lbnRzJHNjb3JlJEcyTSwgeGxhYj0iRzEgc2NvcmUiLCB5bGFiPSJHMi9NIHNjb3JlIiwgcGNoPTE2LCBjZXg9MC42KQpgYGAKCl9fQmFja2dyb3VuZCBDZWxsIEN5Y2xlIEFuYWx5c2lzX18KLSBDZWxscyBhcmUgY2xhc3NpZmllZCBhcyBiZWluZyBpbiBHMSBwaGFzZSAobm90IGluIGNlbGwgZGl2aXNpb24gYWthIGNlbGwgY3ljbGUpIGlmIHRoZWlyIEcxIHNjb3JlIGlzIGFib3ZlIDAuNSBhbmQgZ3JlYXRlciB0aGFuIHRoZSBHMi9NIHNjb3JlLgotIENlbGxzIGFyZSBjbGFzc2lmaWVkIGFzIGJlaW5nIGluIFMgcGhhc2UgKHN5bnRoZXNpcyBvZiBETkEsIHJlcGxpY2F0aW9uKSBpZiBuZWl0aGVyIHNjb3JlIGlzIGFib3ZlIDAuNS4KLSBDZWxscyBhcmUgY2xhc3NpZmllZCBhcyBiZWluZyBpbiBHMi9NIHBoYXNlIChnYXAgYmV0d2VlbiBETkEgc3ludGhlc2lzIGFuZCBtaXRvc2lzKSBpZiB0aGVpciBHMi9NIHNjb3JlIGlzIGFib3ZlIDAuNSBhbmQgZ3JlYXRlciB0aGFuIHRoZSBHMSBzY29yZS4KIApUaGUgY2VsbC1jeWNsZSBzdGF0dXMgb2YgY2VsbHMgY2FuIGJlIGEgc2lnbmlmaWNhbnQgY29uZm91bmRpbmcgZmFjdG9yIGluIHNvbWUgZGF0YXNldHMgaS5lLiBjbHVzdGVycyBmb3JtaW5nIG9uIHRoZSBiYXNpcyBvZiBjZWxsIGN5Y2xlIHN0YXR1cyBpbnN0ZWFkIG9mIG90aGVyIGJpb2xvZ2ljYWwgZmFjdG9ycyBvZiBpbnRlcmVzdC4gVGhlIGdvYWwgYXQgdGhpcyBzdGFnZSBpcyBvbmx5IHRvIGFzc2VzcyB0aGUgY2VsbCBjeWNsZSBzdGF0dXMgb2YgY2VsbHMgbm90IHRvIHRyeSBub3JtYWxpc2UgaXQgYXdheS4KClRoaXMgbWV0aG9kIHdvdWxkIGJlIGxlc3MgYWNjdXJhdGUgZm9yIGRhdGEgdGhhdCBhcmUgc3Vic3RhbnRpYWxseSBkaWZmZXJlbnQgZnJvbSB0aG9zZSB1c2VkIGluIHRoZSB0cmFpbmluZyBzZXQsIGUuZy4sIGR1ZSB0byB0aGUgdXNlIG9mIGEgZGlmZmVyZW50IHByb3RvY29sLiBUaGlzIGRhdGFzZXQgdXNlcyBVTUkgY291bnRzLCB3aGljaCBoYXMgYW4gZW50aXJlbHkgZGlmZmVyZW50IHNldCBvZiBiaWFzZXMsIGUuZy4sIDPigJktZW5kIGNvdmVyYWdlIG9ubHksIG5vIGxlbmd0aCBiaWFzLCBubyBhbXBsaWZpY2F0aW9uIG5vaXNlLiBUaGVzZSBuZXcgYmlhc2VzIChhbmQgdGhlIGFic2VuY2Ugb2YgZXhwZWN0ZWQgYmlhc2VzKSBtYXkgaW50ZXJmZXJlIHdpdGggYWNjdXJhdGUgY2xhc3NpZmljYXRpb24gb2Ygc29tZSBjZWxscy4gU28gdGhlcmUgaXMgc29tZSB1bmNlcnRhaW50eSB3aXRoIHRoaXMgYW5hbHlzaXMuIAoKTmV2ZXJ0aGVsZXNzIHdlIG5lZWQgdG8ga2VlcCBpbiBtaW5kIHRoYXQgdGhlcmUgY291bGQgYmUgcXVpdGUgaGlnaCBjZWxsLWN5Y2xlIGVmZmVjdCB3aGljaCBtaWdodCBjb25mb3VuZCB0aGUgZGF0YXNldC4gVG8gYXZvaWQgcHJvYmxlbXMgZnJvbSBtaXNjbGFzc2lmaWNhdGlvbiwgbm8gcHJvY2Vzc2luZyBvZiB0aGlzIGRhdGFzZXQgYnkgY2VsbCBjeWNsZSBwaGFzZSB3aWxsIGJlIGRvbmUgaGVyZS4KCl9fVGhpcyBEYXRhc2V0J3MgQ2VsbCBDeWNsZSByZXN1bHRfXwpJdCBsb29rcyBsaWtlIG1ham9yaXR5IG9mIHRoZSBjZWxscyBoYXZlIGEgaGlnaCBfX0cyL01fXyBvciBfX1NfXyBzY29yZSBpbmRpY2F0aW5nIHRoYXQgdGhlIGNlbGxzIGFyZSBfX3NvbWV3aGF0X18gZ29pbmcgdGhyb3VnaCBjZWxsLWN5Y2xlIHN0YWdlcy4gR2VuZXJhbGx5IGlmIHRoZXkgYXJlIG9uIEcxIHN0YWdlLCB0aGVuIHRoZXkgYXJlIG5vdCBpbiBjZWxsLWN5Y2xlIHN0YWdlIGFuZCBpZiBvbiBHMi9NIHRoZW4gdGhleSBhcmUgY2VsbC1jeWNsZSBzdGFnZXMuIEJ1dCBoZXJlIHRoZXkgYXJlIGluIFMsIHdoaWNoIGlzIHRoZSBzeW50aGVzaXMgcGhhc2UgaW5kaWNhdGluZyBETkEgaXMgYmVpbmcgcmVwbGljYXRlZC4KCgpgYGB7cn0KY29sRGF0YShjZFNjRmlsdCkkQ2VsbEN5Y2xlIDwtIGFzc2lnbm1lbnRzJHBoYXNlcwpjb2xEYXRhKGNkU2NGaWx0QW5ub3QpJENlbGxDeWNsZSA8LSBhc3NpZ25tZW50cyRwaGFzZXMKcGxvdFBDQShjZFNjRmlsdEFubm90LCBjb2xvdXJfYnkgPSAiQ2VsbEN5Y2xlIiwgc2hhcGVfYnk9IlNhbXBsZSIpCmBgYAoKSXQgZG9lcyBub3QgbG9vayBsaWtlIG9uZSBwYXRpZW50IGlzIGRvbWluYXRlZCBieSBhIGNlbGwtY3ljbGUgcGhhc2UuIAoKIyA1LiBGaWx0ZXJpbmcgb3V0IGxvdy1hYnVuZGFuY2UgZ2VuZXM8YSBuYW1lPSdzZWN0aW9uNScgLz4KClRoZSBnb2FsIGlzIHRvIGtlZXAgZ2VuZXMgdGhhdCB3aWxsIGJlIGdvb2QgZmVhdHVyZXMgdG8gZGlzY3JpbWluYXRlIHRoZSBjZWxscy4gCgpMb3ctYWJ1bmRhbmNlIGdlbmVzIGFyZSBwcm9ibGVtYXRpYyBhcyB6ZXJvIG9yIG5lYXItemVybyBjb3VudHMgZG8gbm90IGNvbnRhaW4gZW5vdWdoIGluZm9ybWF0aW9uIGZvciByZWxpYWJsZSBzdGF0aXN0aWNhbCBpbmZlcmVuY2UgKFtCb3VyZ29uIGV0IGFsLiwgMjAxMF0oaHR0cDovL3d3dy5wbmFzLm9yZy9jb250ZW50LzEwNy8yMS85NTQ2KSkuIEluIGFkZGl0aW9uLCB0aGUgZGlzY3JldGVuZXNzIG9mIHRoZSBjb3VudHMgbWF5IGludGVyZmVyZSB3aXRoIGRvd25zdHJlYW0gc3RhdGlzdGljYWwgcHJvY2VkdXJlcywgZS5nLiwgYnkgY29tcHJvbWlzaW5nIHRoZSBhY2N1cmFjeSBvZiBjb250aW51b3VzIGFwcHJveGltYXRpb25zLiAKClRoZSBtb3JlIGxvdyBmcmVxdWVuY3ksIGxvdyBleHByZXNzaW9uIGdlbmVzIHRoZSBtb3JlIG5vaXNlLiAKClRoZXNlIGdlbmVzIGFyZSBsaWtlbHkgdG8gYmUgZG9taW5hdGVkIGJ5IGRyb3Atb3V0IGV2ZW50cyAoW0JyZW5uZWNrZSBldCBhbC4sIDIwMTNdKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvbm1ldGguMjY0NSkpLCB3aGljaCBsaW1pdHMgdGhlaXIgdXNlZnVsbmVzcyBpbiBsYXRlciBhbmFseXNlcy4gUmVtb3ZhbCBvZiB0aGVzZSBnZW5lcyBpbXByb3ZlcyBkaXNjcmV0ZW5lc3MgYW5kIHJlZHVjZXMgdGhlIGFtb3VudCBvZiBjb21wdXRhdGlvbmFsIHdvcmsgd2l0aG91dCBtYWpvciBsb3NzIG9mIGluZm9ybWF0aW9uLgoKR2VuZXJhbGx5IGluIHNjUk5BLXNlcSBsb3ctYWJ1bmRhbmNlIGdlbmVzIGFyZSBkZWZpbmVkIGFzIHRob3NlIHdpdGggYW4gYXZlcmFnZSBjb3VudCBiZWxvdyBhIGZpbHRlciB0aHJlc2hvbGQgb2YgMS4gQnV0IDEwWCBDaHJvbWl1bSBpcyBiYXNlZCBvbiBVTUkgY291bnRzLCB3aGljaCBhcmUgbG93ZXIgYnV0IGJldHRlciBxdWFsaXR5IGNvdW50cywgc28gc2V0dGluZyB0aGUgdGhyZXNob2xkIHRvIDEgd291bGQgZmlsdGVyIGEgbGFyZ2UgbnVtYmVyIG9mIGNlbGxzIHVuZmFpcmx5LiAKCjxmb250IGNvbG9yPXJlZD5JbiB0aGUgYW5hbHlzaXMgYmVsb3cgd2UgZ28gdGhyb3VnaCBhbiBpdGVyYXRpdmUgcHJvY2VzcyB1c2luZyB0aGUgZmlndXJlIGJlbG93LCBzdGFydGluZyB3aXRoIDAuMDEgdG8gYXNzZXNzIHdoYXQgY3V0b2ZmIHRvIHVzZS4KVG8gY2hlY2sgd2hldGhlciB0aGUgY2hvc2VuIHRocmVzaG9sZCBpcyBzdWl0YWJsZSwgd2UgZXhhbWluZSB0aGUgZGlzdHJpYnV0aW9uIG9mIGxvZy1tZWFucyBhY3Jvc3MgYWxsIGdlbmVzIChGaWd1cmUgYmVsb3cpLiBHZW5lcmFsbHkgZm9yIGhpZ2hlciBudW1iZXIgb2YgY2VsbHMgdGhlcmUgaXMgYSBwZWFrIG9uIHRoZSByaWdodCBoYW5kIHNpZGUgdGhhdCByZXByZXNlbnRzIHRoZSBidWxrIG9mIG1vZGVyYXRlbHkgZXhwcmVzc2VkIGdlbmVzIHdoaWxlIGluIHRoZSBtaWRkbGUgdGhlcmUgaXMgYSByZWN0YW5ndWxhciBjb21wb25lbnQgdGhhdCBjb3JyZXNwb25kcyB0byBsb3dseSBleHByZXNzZWQgZ2VuZXMuIFRoZSBmaWx0ZXIgdGhyZXNob2xkIHNob3VsZCBjdXQgdGhlIGRpc3RyaWJ1dGlvbiBhdCBzb21lIHBvaW50IGFsb25nIHRoZSByZWN0YW5ndWxhciBjb21wb25lbnQgdG8gcmVtb3ZlIHRoZSBtYWpvcml0eSBvZiBsb3ctYWJ1bmRhbmNlIGdlbmVzLiBBcyB0aGUgYmx1ZSBsaW5lIHJlcHNyZXNlbnRzIGluIHRoZSBmaWd1cmUgYmVsb3csIGl0IGN1dHMgdGhlIGNvdW50cyBhdCB0aGUgcmVjdGFuZ3VsYXIgY29tcG9uZW50LiBHZW5lcmFsbHkgOSwwMDAgLSAxNCwwMDAgZ2VuZXMgaXMgZ29vZCAobW91c2UpIGFuZCA3LDUwMC0xNCwwMDAgZm9yIGh1bWFuLgo8L2ZvbnQ+CgoKYGBge3J9CmF2ZS5jb3VudHMgPC0gcm93TWVhbnMoY291bnRzKGNkU2NGaWx0KSkKa2VlcEEgPC0gYXZlLmNvdW50cyA+PSAwLjAxCmtlZXBCIDwtIGF2ZS5jb3VudHMgPj0gMC4wMgpwcmludChwYXN0ZTAoJ0xvZzEwIGF2IGNvdW50ID49MC4wMSBsZWF2ZXMgJyxhcy5jaGFyYWN0ZXIoc3VtKGtlZXBBKSksICcgZ2VuZXMgJykpCnByaW50KHBhc3RlMCgnTG9nMTAgYXYgY291bnQgPj0wLjAyIGxlYXZlcyAnLGFzLmNoYXJhY3RlcihzdW0oa2VlcEIpKSwgJyBnZW5lcyAnKSkKYGBgCgpgYGB7cn0KIyBwbG90IG9mIGJvdGggY3V0IG9mZnMKaGlzdChsb2cxMChhdmUuY291bnRzKSwgYnJlYWtzPTEwMCwgbWFpbj0iIiwgY29sPSJncmV5ODAiLAogICAgIHhsYWI9ZXhwcmVzc2lvbihMb2dbMTBdfiJhdmVyYWdlIGNvdW50IikpCmFibGluZSh2PWxvZzEwKDAuMDIpLCBjb2w9ImJsdWUiLCBsd2Q9MiwgbHR5PTIpCnRleHQobG9nMTAoMC4wMzUpLDEwMDAsICIwLjAyIikKYWJsaW5lKHY9bG9nMTAoMC4wMSksIGNvbD0iYmx1ZSIsIGx3ZD0yLCBsdHk9MikKdGV4dChsb2cxMCgwLjAwNiksIDEwMDAsICIwLjAxIikKYGBgCgoKQWZ0ZXIgZmlsdGVyaW5nIHdpdGggYXZlcmFnZSBjb3VudCBvZiAwLjAxIHRoZXJlIGFyZSAxMzE5NCBnZW5lcyBsZWZ0IGZvciB0aGUgZG93bnN0ZXJhbSBhbmFseXNpcy4gCgpUbyBjaGVjayB3aGV0aGVyIHRoZSBjaG9zZW4gdGhyZXNob2xkIGlzIHN1aXRhYmxlLCB3ZSBleGFtaW5lIHRoZSBkaXN0cmlidXRpb24gb2YgbG9nLW1lYW5zIGFjcm9zcyBhbGwgZ2VuZXMgKEZpZ3VyZSBiZWxvdykuIEdlbmVyYWxseSBmb3IgaGlnaGVyIG51bWJlciBvZiBjZWxscyB0aGVyZSBpcyBhIHBlYWsgb24gdGhlIHJpZ2h0IGhhbmQgc2lkZSB0aGF0IHJlcHJlc2VudHMgdGhlIGJ1bGsgb2YgbW9kZXJhdGVseSBleHByZXNzZWQgZ2VuZXMgd2hpbGUgaW4gdGhlIG1pZGRsZSB0aGVyZSBpcyBhIHJlY3Rhbmd1bGFyIGNvbXBvbmVudCB0aGF0IGNvcnJlc3BvbmRzIHRvIGxvd2x5IGV4cHJlc3NlZCBnZW5lcy4gVGhlIGZpbHRlciB0aHJlc2hvbGQgc2hvdWxkIGN1dCB0aGUgZGlzdHJpYnV0aW9uIGF0IHNvbWUgcG9pbnQgYWxvbmcgdGhlIHJlY3Rhbmd1bGFyIGNvbXBvbmVudCB0byByZW1vdmUgdGhlIG1ham9yaXR5IG9mIGxvdy1hYnVuZGFuY2UgZ2VuZXMuIEFzIHRoZSBibHVlIGxpbmUgcmVwc3Jlc2VudHMgaW4gdGhlIGZpZ3VyZSBiZWxvdywgaXQgY3V0cyB0aGUgY291bnRzIGF0IHRoZSByZWN0YW5ndWxhciBjb21wb25lbnQuCgoKV2Ugd2lsbCBsb29rIGF0IHRoZSBpZGVudGl0aWVzIG9mIHRoZSBtb3N0IGhpZ2hseSBleHByZXNzZWQgZ2VuZXMgYmVmb3JlIGZpbHRlcmluZyB0aGVtLiBUaGlzIHNob3VsZCBnZW5lcmFsbHkgYmUgZG9taW5hdGVkIGJ5IGNvbnN0aXR1dGl2ZWx5IGV4cHJlc3NlZCB0cmFuc2NyaXB0cywgc3VjaCBhcyB0aG9zZSBmb3Igcmlib3NvbWFsIG9yIG1pdG9jaG9uZHJpYWwgcHJvdGVpbnMuIFRoZSBwcmVzZW5jZSBvZiBvdGhlciBjbGFzc2VzIG9mIGZlYXR1cmVzIG1heSBiZSBjYXVzZSBmb3IgY29uY2VybiBpZiB0aGV5IGFyZSBub3QgY29uc2lzdGVudCB3aXRoIGV4cGVjdGVkIGJpb2xvZ3kuIEZvciBleGFtcGxlLCB0aGUgYWJzZW5jZSBvZiByaWJvc29tYWwgcHJvdGVpbnMgYW5kL29yIHRoZSBwcmVzZW5jZSBvZiB0aGVpciBwc2V1ZG9nZW5lcyBhcmUgaW5kaWNhdGl2ZSBvZiBzdWJvcHRpbWFsIGFsaWdubWVudC4KCmBgYHtyfQpwbG90SGlnaGVzdEV4cHJzKGNkU2NGaWx0QW5ub3QpCmBgYAoKYGBge3J9CnBkZignSGlnaGVzdF9leHByZXNzaW9uXzUwR2VuZS5wZGYnKQpwbG90SGlnaGVzdEV4cHJzKGNkU2NGaWx0QW5ub3QpCmRldi5vZmYoKQpgYGAKX19DaGVjayBpZGVudGl0aWVzIG9mIHRoZSBtb3N0IGhpZ2hseSBleHByZXNzZWQgZ2VuZXMgYmVmb3JlIGZpbHRlcmluZyB0aGVtX18uIFRoaXMgc2hvdWxkIGdlbmVyYWxseSBiZSBkb21pbmF0ZWQgYnkgY29uc3RpdHV0aXZlbHkgZXhwcmVzc2VkIHRyYW5zY3JpcHRzLCBzdWNoIGFzIHRob3NlIGZvciByaWJvc29tYWwgb3IgbWl0b2Nob25kcmlhbCBwcm90ZWlucy4gVGhlIHByZXNlbmNlIG9mIG90aGVyIGNsYXNzZXMgb2YgZmVhdHVyZXMgbWF5IGJlIGNhdXNlIGZvciBjb25jZXJuIGlmIHRoZXkgYXJlIG5vdCBjb25zaXN0ZW50IHdpdGggZXhwZWN0ZWQgYmlvbG9neS4gRm9yIGV4YW1wbGUsIHRoZSBhYnNlbmNlIG9mIHJpYm9zb21hbCBwcm90ZWlucyBhbmQvb3IgdGhlIHByZXNlbmNlIG9mIHRoZWlyIHBzZXVkb2dlbmVzIGFyZSBpbmRpY2F0aXZlIG9mIHN1Ym9wdGltYWwgYWxpZ25tZW50LgoKUGxvdCBzaG93cyBwZXJjZW50YWdlIG9mIHRvdGFsIGNvdW50cyAocGVyIGNlbGwpIGFzc2lnbmVkIHRvIHRoZSB0b3AgNTAgbW9zdCBoaWdobHktYWJ1bmRhbnQgZmVhdHVyZXMgaW4gdGhlIGRhdGFzZXQuCgpGb3IgZWFjaCBmZWF0dXJlLCBlYWNoIGJhciByZXByZXNlbnRzIHRoZSBwZXJjZW50YWdlIGFzc2lnbmVkIHRvIHRoYXQgZmVhdHVyZSBmb3IgYSBjZWxsLCB3aGlsZSB0aGUgY2lyY2xlIHJlcHJlc2VudHMgdGhlIGF2ZXJhZ2UgYWNyb3NzIGFsbCBjZWxscy4gQmFycyBhcmUgY29sb3VyZWQgYnkgdGhlIHRvdGFsIG51bWJlciBvZiBleHByZXNzZWQgZmVhdHVyZXMgaW4gZWFjaCBjZWxsLCB3aGlsZSBjaXJjbGVzIGFyZSBjb2xvdXJlZCBhY2NvcmRpbmcgdG8gd2hldGhlciB0aGUgZmVhdHVyZSBpcyBsYWJlbGxlZCBhcyBhIGNvbnRyb2wgZmVhdHVyZS4KCjxmb250IGNvbG9yPXJlZD5fX0RvIHRoZSB0b3Btb3N0IGdlbmVzIGFwcGVhciB0byBhZ3JlZSB3aXRoIHRoZSBiaW9sb2d5P19fPC9mb250PgoKPGZvbnQgY29sb3I9cmVkPl9fTk9URSBwZXJtYW5lbnQgY2hhbmdlIHRvIGNkU2NGaWx0QW5ub3QgbmV4dCFfXzwvZm9udD4KClRoaXMgbWVhbi1iYXNlZCBmaWx0ZXIgdGVuZHMgdG8gYmUgbGVzcyBhZ2dyZXNzaXZlLiBBIGdlbmUgd2lsbCBiZSByZXRhaW5lZCBhcyBsb25nIGFzIGl0IGhhcyBzdWZmaWNpZW50IGV4cHJlc3Npb24gaW4gYW55IHN1YnNldCBvZiBjZWxscy4gR2VuZXMgZXhwcmVzc2VkIGluIGZld2VyIGNlbGxzIHJlcXVpcmUgaGlnaGVyIGxldmVscyBvZiBleHByZXNzaW9uIGluIHRob3NlIGNlbGxzIHRvIGJlIHJldGFpbmVkLCBidXQgdGhpcyBpcyBub3QgdW5kZXNpcmFibGUgYXMgaXQgYXZvaWRzIHNlbGVjdGluZyB1bmluZm9ybWF0aXZlIGdlbmVzICh3aXRoIGxvdyBleHByZXNzaW9uIGluIGZldyBjZWxscykgdGhhdCBjb250cmlidXRlIGxpdHRsZSB0byBkb3duc3RyZWFtIGFuYWx5c2VzLCBlLmcuLCBIVkcgZGV0ZWN0aW9uIG9yIGNsdXN0ZXJpbmcuIEluIGNvbnRyYXN0LCB0aGUg4oCcYXQgbGVhc3QgbuKAnSBmaWx0ZXIgZGVwZW5kcyBoZWF2aWx5IG9uIHRoZSBjaG9pY2Ugb2Ygbi4gV2l0aCBuID0gMTAsIGEgZ2VuZSBleHByZXNzZWQgaW4gYSBzdWJzZXQgb2YgOSBjZWxscyB3b3VsZCBiZSBmaWx0ZXJlZCBvdXQsIHJlZ2FyZGxlc3Mgb2YgdGhlIGxldmVsIG9mIGV4cHJlc3Npb24gaW4gdGhvc2UgY2VsbHMuIFRoaXMgbWF5IHJlc3VsdCBpbiB0aGUgZmFpbHVyZSB0byBkZXRlY3QgcmFyZSBzdWJwb3B1bGF0aW9ucyB0aGF0IGFyZSBwcmVzZW50IGF0IGZyZXF1ZW5jaWVzIGJlbG93IG4uIFdoaWxlIHRoZSBtZWFuLWJhc2VkIGZpbHRlciB3aWxsIHJldGFpbiBtb3JlIG91dGxpZXItZHJpdmVuIGdlbmVzLCB0aGlzIGNhbiBiZSBoYW5kbGVkIGJ5IGNob29zaW5nIG1ldGhvZHMgdGhhdCBhcmUgcm9idXN0IHRvIG91dGxpZXJzIGluIHRoZSBkb3duc3RyZWFtIGFuYWx5c2VzLgpUaHVzLCB3ZSBhcHBseSB0aGUgbWVhbi1iYXNlZCBmaWx0ZXIgdG8gdGhlIGRhdGEgYnkgc3Vic2V0dGluZyB0aGUgU0NFU2V0IG9iamVjdCBhcyBzaG93biBiZWxvdy4gCgpgYGB7cn0KYXZlLmNvdW50cyA8LSByb3dNZWFucyhjb3VudHMoY2RTY0ZpbHQpKQprZWVwIDwtIGF2ZS5jb3VudHMgPj0gMC4wMQpwcmludChwYXN0ZTAoJ0xvZzEwIGF2IGNvdW50IGFkanVzdG1lbnQgbm93IGxlYXZlcyAnLGFzLmNoYXJhY3RlcihzdW0oa2VlcCkpLCAnIGdlbmVzICcpKQojc3VtKGtlZXApCmBgYAoKYGBge3J9CmNkU2NGaWx0IDwtIGNkU2NGaWx0W2tlZXAsXQpjZFNjRmlsdEFubm90IDwtIGNkU2NGaWx0CiNjZFNjRmlsdEFubm90IDwtIGNhbGN1bGF0ZVFDTWV0cmljcyhjZFNjRmlsdEFubm90KQpgYGAKYGBge3J9CmNkU2NGaWx0QW5ub3QKYGBgCgpUaGUgZmlsdGVyaW5nIG5vdyBsZXRzIHNlZSBob3cgdGhlIHRvdGFsIGNvdW50cyBhbmQgZmVhdHVyZXMgcGxvdHM6CmBgYHtyfQpwIDwtIHBsb3RDb2xEYXRhKGNkU2NGaWx0LCB4PSJsb2cxMF90b3RhbF9jb3VudHMiLHk9InRvdGFsX2ZlYXR1cmVzX2J5X2NvdW50cyIsIGNvbG91cl9ieSA9ICJTYW1wbGUiKSAgKyBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9Y19zYW1wbGVfY29sKSAKcApgYGAKCldoYXQgaXMgdGhlIGZyZXF1ZW5jeSBvZiB0aGUgY2VsbHMgd2l0aCBtZWFuIGV4cHJlc3Npb24/OgpgYGB7cn0KcGxvdEV4cHJzRnJlcVZzTWVhbihjZFNjRmlsdEFubm90KQpgYGAKCl9fRnJlcXVlbmN5IG9mIHRoZSBjZWxscyB2ZXJzdXMgbWVhbiBleHByZXNzaW9uLl9fIFByZWZlcmFibHkgYXQgbGVhc3QgMTAwMCBnZW5lcyB3aWxsIGJlIGV4cHJlc3NlZCBpbiA1MCUgb2YgdGhlIGNlbGxzLiBUaGVzZSB1bmlmb3JtbHkgZXhwcmVzc2VkIGdlbmVzIGFyZSBub3QgaGVscGZ1bCBpbiBkaXNjaW1pbmF0aW5nIGNlbGxzLCBidXQgaWYgdGhlIG51bWJlciBvZiBnZW5lcyBleHByZXNzZWQgaW4gPjUwJSBpcyBsb3csIHRoYXQgbWVhbnMgdGhlIGRhdGFzZXQgcXVhbGl0eSBpcyBub3QgZ3JlYXQgYmVjYXVzZSB0b28gbWFueSBkcm9wb3V0cy4gCgpgYGB7cn0Kc2F2ZS5pbWFnZSgpCmBgYAoKCl9fU28gdGhpbmdzIGxvb2sgZmluZSBpbiBRQy5fXwoKIyA2LiBOb3JtYWxpemF0aW9uIG9mIHJlYWQgY291bnRzPGEgbmFtZT0nc2VjdGlvbjYnIC8+ClNpbmdsZSBjZWxsIFJOQS1zZXEgZGF0YSByZXF1aXJlcyBkaWZmZXJlbnQgbm9ybWFsaXNhdGlvbiB0byBidWxrIGRhdGEgbWV0aG9kcyAoZS5nLiBERVNlcTIpIGJlY2F1c2Ugc2NSTkEtc2VxIGlzIHZlcnkgc3BhcnNlLiBIZXJlIHRoZSBkZWNvbnZvbHV0aW9uIGJhc2VkIG1ldGhvZCB3aWxsIGJlIHVzZWQuCgpfX0Z1cnRoZXIgZGV0YWlsIG9uIHRoZSBkZWNvbnZvbHV0aW9uIG1ldGhvZCB0byBkZWFsIHdpdGggemVybyBjb3VudHM6X18gClJlYWQgY291bnRzIGFyZSBzdWJqZWN0IHRvIGRpZmZlcmVuY2VzIGluIGNhcHR1cmUgZWZmaWNpZW5jeSBhbmQgc2VxdWVuY2luZyBkZXB0aCBiZXR3ZWVuIGNlbGxzIChbU3RlZ2xlIGV0IGFsLiwgMjAxNV0oaHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9ucmczODMzKSkuIE5vcm1hbGl6YXRpb24gaXMgcmVxdWlyZWQgdG8gZWxpbWluYXRlIHRoZXNlIGNlbGwtc3BlY2lmaWMgYmlhc2VzIHByaW9yIHRvIGRvd25zdHJlYW0gcXVhbnRpdGF0aXZlIGFuYWx5c2VzLiBJbiBidWxrIGRhdGEgdGhpcyBpcyBvZnRlbiBkb25lIGJ5IGFzc3VtaW5nIHRoYXQgbW9zdCBnZW5lcyBhcmUgbm90IGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCAoREUpIGJldHdlZW4gY2VsbHMuIEFueSBzeXN0ZW1hdGljIGRpZmZlcmVuY2UgaW4gY291bnQgc2l6ZSBhY3Jvc3MgdGhlIG5vbi1ERSBtYWpvcml0eSBvZiBnZW5lcyBiZXR3ZWVuIHR3byBjZWxscyBpcyBhc3N1bWVkIHRvIHJlcHJlc2VudCBiaWFzIGFuZCBpcyByZW1vdmVkIGJ5IHNjYWxpbmcuIE1vcmUgc3BlY2lmaWNhbGx5LCDigJxzaXplIGZhY3RvcnPigJ0gYXJlIGNhbGN1bGF0ZWQgdGhhdCByZXByZXNlbnQgdGhlIGV4dGVudCB0byB3aGljaCBjb3VudHMgc2hvdWxkIGJlIHNjYWxlZCBpbiBlYWNoIGxpYnJhcnkuIFNpbmdsZS1jZWxsIGRhdGEgY2FuIGJlIHByb2JsZW1hdGljIGR1ZSB0byB0aGUgZG9taW5hbmNlIG9mIGxvdyBhbmQgemVybyBjb3VudHMuIFRvIG92ZXJjb21lIHRoaXMsIGNvdW50cyBmcm9tIG1hbnkgY2VsbHMgYXJlIHBvb2xlZCB0byBpbmNyZWFzZSB0aGUgY291bnQgc2l6ZSBmb3IgYWNjdXJhdGUgc2l6ZSBmYWN0b3IgZXN0aW1hdGlvbiAoW0x1biBldCBhbC4sIDIwMTZdKGh0dHBzOi8vZ2Vub21lYmlvbG9neS5iaW9tZWRjZW50cmFsLmNvbS9hcnRpY2xlcy8xMC4xMTg2L3MxMzA1OS0wMTYtMDk0Ny03KSkuIFBvb2wtYmFzZWQgc2l6ZSBmYWN0b3JzIGFyZSB0aGVuIOKAnGRlY29udm9sdmVk4oCdIGludG8gY2VsbC1iYXNlZCBmYWN0b3JzIGZvciBjZWxsLXNwZWNpZmljIG5vcm1hbGl6YXRpb24uCgpgYGB7cn0KY2RTY0ZpbHRBbm5vdCA8LSBjb21wdXRlU3VtRmFjdG9ycyhjZFNjRmlsdEFubm90LCBzaXplcz1jKDEwLCA1MCwgMTAwLCAxNTApKQpzdW1tYXJ5KHNpemVGYWN0b3JzKGNkU2NGaWx0QW5ub3QpKQpgYGAKCklmIHRoZSBzaXplIGZhY3RvcnMgYXJlIHRpZ2h0bHkgY29ycmVsYXRlZCB3aXRoIHRoZSBsaWJyYXJ5IHNpemVzIGZvciBhbGwgY2VsbHMgdGhpcyB3b3VsZCBzdWdnZXN0cyB0aGF0IHRoZSBzeXN0ZW1hdGljIGRpZmZlcmVuY2VzIGJldHdlZW4gY2VsbHMgYXJlIHByaW1hcmlseSBkcml2ZW4gYnkgZGlmZmVyZW5jZXMgaW4gY2FwdHVyZSBlZmZpY2llbmN5IG9yIHNlcXVlbmNpbmcgZGVwdGguIEFueSBERSBiZXR3ZWVuIGNlbGxzIHdvdWxkIHlpZWxkIGEgbm9uLWxpbmVhciB0cmVuZCBiZXR3ZWVuIHRoZSB0b3RhbCBjb3VudCBhbmQgc2l6ZSBmYWN0b3IsIGFuZC9vciBpbmNyZWFzZWQgc2NhdHRlciBhcm91bmQgdGhlIHRyZW5kLgoKPGZvbnQgY29sb3I9cmVkPklmIHRoZXJlIGlzIGFuIGVycm9yICJlc3RpbWF0ZWQgbmVnYXRpdmUgc2l6ZSBmYWN0b3IiLCB0aGVuIHRoZSBzaXplcyBoYXMgdG8gYmUgYWRqdXN0ZWRfXzwvZm9udD4KCmBgYHtyfQpERiA8LSBkYXRhLmZyYW1lKFZBUjE9c2l6ZUZhY3RvcnMoY2RTY0ZpbHRBbm5vdCksIFZBUjI9Y2RTY0ZpbHRBbm5vdCR0b3RhbF9jb3VudHMvMWU2KQptb2RlbCA9IGxtKFZBUjIgfiBWQVIxLCBERikKc3VtbWFyeShtb2RlbCkKYGBgCmBgYHtyfQpzcCA9IGdncGxvdChERiwgYWVzKHg9VkFSMSwgeT1WQVIyKSkKc3AgKyBnZW9tX3BvaW50KCkgKyBzdGF0X3Ntb290aChtZXRob2Q9bG0pICsgdGhlbWVfbGlnaHQoYmFzZV9zaXplPTE1KSArCiAgICAgICAgdGhlbWUoc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICBwYW5lbC5ib3JkZXIgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArICAKICAgICAgICAgICAgICB4bGFiKCJTaXplIEZhY3RvciIpICsgCiAgICAgICAgICAgICAgeWxhYigiTGlicmFyeSBTaXplIChtaWxsaW9ucykiKSsKICAgICAgICAgICAgICBhbm5vdGF0ZSgidGV4dCIsIGxhYmVsPSJSXjI9MC45MCIsIHg9Mi44LCB5PTAuMDI1KQpgYGAKSWYgdGhlIHNpemUgZmFjdG9ycyBhcmUgdGlnaHRseSBjb3JyZWxhdGVkIHdpdGggdGhlIGxpYnJhcnkgc2l6ZXMgZm9yIGFsbCBjZWxscyB0aGlzIHdvdWxkIHN1Z2dlc3RzIHRoYXQgdGhlIHN5c3RlbWF0aWMgZGlmZmVyZW5jZXMgYmV0d2VlbiBjZWxscyBhcmUgcHJpbWFyaWx5IGRyaXZlbiBieSBkaWZmZXJlbmNlcyBpbiBjYXB0dXJlIGVmZmljaWVuY3kgb3Igc2VxdWVuY2luZyBkZXB0aC4gQW55IERFIGJldHdlZW4gY2VsbHMgd291bGQgeWllbGQgYSBub24tbGluZWFyIHRyZW5kIGJldHdlZW4gdGhlIHRvdGFsIGNvdW50IGFuZCBzaXplIGZhY3RvciwgYW5kL29yIGluY3JlYXNlZCBzY2F0dGVyIGFyb3VuZCB0aGUgdHJlbmQuCgpXaXRoIGEgaGlnaCBSXjIgdmFsdWUsIHRoZSBzaXplIGZhY3RvcnMgYXJlIHZlcnkgdGlnaHRseSBjb3JyZWxhdGVkIHdpdGggdGhlIGxpYnJhcnkgc2l6ZS4gSSBjYWxjdWxhdGUgdGhlIHNpemUgZmFjdG9yIG5vcm1pemF0aW9uIGFuZCBwdXQgaXQgaW4gYSBzbG90IG9mIGBTdW1tYXJpemVkRXhwZXJpbWVudGAgb2JqZWN0LgoKCl9fQXBwbHlpbmcgdGhlIHNpemUgZmFjdG9ycyB0byBub3JtYWxpemUgZ2VuZSBleHByZXNzaW9uOl9fCgpUaGUgY291bnQgZGF0YSBhcmUgdXNlZCB0byBjb21wdXRlIG5vcm1hbGl6ZWQgbG9nLWV4cHJlc3Npb24gdmFsdWVzIGZvciB1c2UgaW4gZG93bnN0cmVhbSBhbmFseXNlcy4gRWFjaCB2YWx1ZSBpcyBkZWZpbmVkIGFzIHRoZSBsb2ctcmF0aW8gb2YgZWFjaCBjb3VudCB0byB0aGUgc2l6ZSBmYWN0b3IgZm9yIHRoZSBjb3JyZXNwb25kaW5nIGNlbGwsIGFmdGVyIGFkZGluZyBhIHByaW9yIGNvdW50IG9mIDEgdG8gYXZvaWQgdW5kZWZpbmVkIHZhbHVlcyBhdCB6ZXJvIGNvdW50cy4gRGl2aXNpb24gYnkgdGhlIHNpemUgZmFjdG9yIGVuc3VyZXMgdGhhdCBhbnkgY2VsbC1zcGVjaWZpYyBiaWFzZXMgYXJlIHJlbW92ZWQuIElmIHNwaWtlLWluLXNwZWNpZmljIHNpemUgZmFjdG9ycyBhcmUgcHJlc2VudCBpbiBzY2UsIHRoZXkgd2lsbCBiZSBhdXRvbWF0aWNhbGx5IGFwcGxpZWQgdG8gbm9ybWFsaXplIHRoZSBzcGlrZS1pbiB0cmFuc2NyaXB0cyBzZXBhcmF0ZWx5IGZyb20gdGhlIGVuZG9nZW5vdXMgZ2VuZXMuCgojIyMgUmUtY2FsY3VsYXRlIHRoZSBDUE0gbm9ybWFsaXphdGlvbiB1c2luZyBzaXplIGZhY3RvcnMKClJlY2FsY3VsYXRlIHRoZSBgY291bnRzLXBlci1taWxsaW9uYCBmb3IgdGhlc2UgY2VsbHMuIFRoaXMgaXMgYmVjYXVzZSBsYXJnZSBudW1iZXIgb2YgZ2VuZXMgaGF2ZSBiZWVuIGZpbHRlcmVkIG91dCB3aGljaCB3b3VsZCBoYXZlIGltcGFjdGVkIHRoZSBsaWJyYXJ5IHNpemUgd2hlbiBDUE0gd2FzIGNhbGN1YWx0ZWQgd2l0aCB1bmZpbHRlcmVkIGRhdGEuIE5vdywgYXMgdGhlIG51bWJlciBvZiBnZW5lcyBoYXZlIGJlZW4gcmVkdWNlZCwgc28gaXMgdGhlIGxpYnJhcnkgc2l6ZS4gCgpgYGB7cn0KIyBjcG0oY2RTY0ZpbHRBbm5vdCkgPC0gbG9nMihjYWxjdWxhdGVDUE0oY2RTY0ZpbHRBbm5vdCwgdXNlX3NpemVfZmFjdG9ycyA9IFRSVUUpICsgMSkKZXhwcnMoY2RTY0ZpbHRBbm5vdCkgPC0gbG9nMihjYWxjdWxhdGVDUE0oY2RTY0ZpbHRBbm5vdCwgdXNlX3NpemVfZmFjdG9ycyA9IFRSVUUpICsgMSkKYGBgCgpTZXR0aW5nIHRoZSBwYXJhbWV0ZXIgYHVzZS5zaXplLmZhY3RvcnMgPSBUUlVFYCBlbmFibGVzIHRoZSB1c2Ugb2Ygc3RlcCBzaXplcyBjYWxjdWxhdGVkIGluIHRoZSBhYm92ZSBwb29sIGJhc2VkIG1ldGhvZC4KClRoZSBsb2ctdHJhbnNmb3JtYXRpb24gcHJvdmlkZXMgc29tZSBtZWFzdXJlIG9mIHZhcmlhbmNlIHN0YWJpbGl6YXRpb24gKFtMYXcgZXQgYWwuLCAyMDE0XShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUM0MDUzNzIxLykpLCBzbyB0aGF0IGhpZ2gtYWJ1bmRhbmNlIGdlbmVzIHdpdGggbGFyZ2UgdmFyaWFuY2VzIGRvIG5vdCBkb21pbmF0ZSBkb3duc3RyZWFtIGFuYWx5c2VzLiBUaGUgY29tcHV0ZWQgdmFsdWVzIGFyZSBzdG9yZWQgYXMgYW4gZXhwcnMgbWF0cml4IGluIGFkZGl0aW9uIHRvIHRoZSBvdGhlciBhc3NheSBlbGVtZW50cy4KCgojIyBDaGVja2luZyBmb3IgaW1wb3J0YW50IHRlY2huaWNhbCBmYWN0b3JzCgpXZSBjaGVjayB3aGV0aGVyIHRoZXJlIGFyZSB0ZWNobmljYWwgZmFjdG9ycyB0aGF0IGNvbnRyaWJ1dGUgc3Vic3RhbnRpYWxseSB0byB0aGUgaGV0ZXJvZ2VuZWl0eSBvZiBnZW5lIGV4cHJlc3Npb24uIElmIHNvLCB0aGUgZmFjdG9yIG1heSBuZWVkIHRvIGJlIHJlZ3Jlc3NlZCBvdXQgdG8gZW5zdXJlIHRoYXQgaXQgZG9lcyBub3QgaW5mbGF0ZSB0aGUgdmFyaWFuY2VzIG9yIGludHJvZHVjZSBzcHVyaW91cyBjb3JyZWxhdGlvbnMuIEZvciB0aGlzIGRhdGFzZXQsIHRoZSBzaW1wbGUgZXhwZXJpbWVudGFsIGRlc2lnbiBtZWFucyB0aGF0IHRoZXJlIGFyZSBubyBwbGF0ZSBvciBiYXRjaCBlZmZlY3RzIHRvIGV4YW1pbmUuIEhvd2V2ZXIgdGhlcmUgY291bGQgYmUgb3RoZXIgdGVjaG5pY2FsIGZhY3RvcnMgbGlrZSB0aGUgY2VsbC1jeWNsZSBlZmZlY3Qgb3IgZHJvcG91dHMuCgpCZWZvcmUgbm9ybWFsaXphdGlvbi4KYGBge3J9CnBsb3RFeHBsYW5hdG9yeVZhcmlhYmxlcyhjZFNjRmlsdEFubm90LCBleHByc192YWx1ZXMgPSAiY291bnRzIiwgdmFyaWFibGVzPWMoInRvdGFsX2ZlYXR1cmVzX2J5X2NvdW50cyIsICJ0b3RhbF9jb3VudHMiLCAiQ2VsbEN5Y2xlIiwgInBjdF9jb3VudHNfTXQiLCAiU2FtcGxlIikpCmBgYAoKCkFmdGVyIG5vcm1hbGl6YXRpb24KYGBge3J9CnBsb3RFeHBsYW5hdG9yeVZhcmlhYmxlcyhjZFNjRmlsdEFubm90LCB2YXJpYWJsZXM9YygidG90YWxfZmVhdHVyZXNfYnlfY291bnRzIiwgInRvdGFsX2NvdW50cyIsICJDZWxsQ3ljbGUiLCAicGN0X2NvdW50c19NdCIsICJTYW1wbGUiKSkKYGBgCgpfX0Fzc2Vzc2luZyB0aGlzIHBsb3QuX18gClR5cGljYWxseSB0aGUgbnVtYmVyIG9mIHRvdGFsIGZlYXR1cmVzIChnZW5lcykgYW5kIHRvdGFsIGNvdW50cyBleHBsYWluIHRoZSBtYWpvciB2YXJpYWJpbGl0eSBpbiBzY1JOQS1zZXEgZGF0YXNldHMuIFRoaXMgaW5kaWNhdGVzIHRoYXQgY2VsbHMgdmFyeSBzaWduaWZpY2FudGx5IGJhc2VkIG9uIG51bWJlciBvZiBnZW5lcyB0aGF0IHRoZXkgYXJlIGV4cHJlc3NpbmcuIAoKTnVtYmVyIG9mIHRvdGFsIGZlYXR1cmVzIGV4cGxhaW5pbmcgbWFqb3JpdHkgb2YgdGhlIHZhcmlhYmlsaXR5IGlzIGEgY29tbW9uIGlzc3VlIGluIHNpbmdsZS1jZWxsIFJOQS1zZXEuIFRoaXMgY291bGQgYmUgZHVlIHRvIHRoZSBzcGFyc2l0eSBvZiB0aGUgZGF0YS4gVGhlIFBDQSBwbG90IG1pZ2h0IGJlIHNlcGFyYXRpbmcgY2VsbHMganVzdCBiZWNhdXNlIHRoZXkgaGF2ZSB2ZXJ5IGRpZmZlcmVudCBudW1iZXIgb2YgZmVhdHVyZXMgZXhwcmVzc2VkIGluIHRvdGFsLgoKSG93ZXZlciwgdGhpcyB3b3VsZCBub3QgY29uZm91bmQgdGhlIHQtU05FIHBsb3Qgd2hpY2ggd291bGQgdGFrZSB0aGUgbm9uLWxpbmVhciByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgdmFyaWFibGVzLiBBcyBsb25nIGFzIHRoZSBwZWFrcyBmb3IgX3RvdGFsX2NvdW50c18gYW5kIF90b3RhbF9mZWF0dXJlX2J5X2NvdW50c18gYXJlIGJlbG93IDEwIHRoaXMgaXMgbm9ybWFsLgoKSW4gdGhlIGNlbGwtY3ljbGUgcGxvdCBzaG93biBlYXJsaWVyIGFuZCB0aGlzIHBsb3QgaGVscCBlc3RhYmxpc2ggdGhlIGltcG9ydGFuY2Ugb2YgdGhlIGNlbGwtY3ljbGUgZWZmZWN0LgoKIyA3LiBJZGVudGlmeWluZyBnZW5lcyBmb3IgZmVhdHVyZSBzZWxlY3Rpb24gKEhWRyk8YSBuYW1lPSdzZWN0aW9uNycgLz4KClRoZSBnb2FsIGlzIHRvIGlkZW50aWZ5IGdlbmVzIHRoYXQgd2lsbCBiZSBnb29kIGZlYXR1cmVzIHRvIGRpc2NyaW1pbmF0ZSB0aGUgY2VsbHMuIEluIHRoaXMgc3RlcCwgdGhlIGRhdGFzZXQgaXMgZmlsdGVyZWQgdG8ga2VlcCBvbmx5IGdlbmVzIHRoYXQgYXJlIOKAnGluZm9ybWF0aXZl4oCdIG9mIHRoZSB2YXJpYWJpbGl0eSBpbiB0aGUgZGF0YS4gSGlnaGx5IFZhcmlhYmxlIEdlbmVzIChIVkcpIGFyZSB0aGUgb25lcyBkcml2aW5nIGhldGVyb2dlbmVpdHkgYWNyb3NzIHRoZSBwb3B1bGF0aW9uIG9mIGNlbGxzLiAKCl9fRGV0YWlsX18gSFZHIGlkZW50aWZpY2F0aW9uIHJlcXVpcmVzIGVzdGltYXRpb24gb2YgdGhlIHZhcmlhbmNlIGluIGV4cHJlc3Npb24gZm9yIGVhY2ggZ2VuZSwgZm9sbG93ZWQgYnkgZGVjb21wb3NpdGlvbiBvZiB0aGUgdmFyaWFuY2UgaW50byBiaW9sb2dpY2FsIGFuZCB0ZWNobmljYWwgY29tcG9uZW50cy4gSFZHcyBhcmUgdGhlbiBpZGVudGlmaWVkIGFzIHRob3NlIGdlbmVzIHdpdGggdGhlIGxhcmdlc3QgYmlvbG9naWNhbCBjb21wb25lbnRzLiBUaGlzIGF2b2lkcyBwcmlvcml0aXppbmcgZ2VuZXMgdGhhdCBhcmUgaGlnaGx5IHZhcmlhYmxlIGR1ZSB0byB0ZWNobmljYWwgZmFjdG9ycywgc3VjaCBhcyBzYW1wbGluZyBub2lzZSBkdXJpbmcgUk5BIGNhcHR1cmUgYW5kIGxpYnJhcnkgcHJlcGFyYXRpb24uCgpJbiB0aGUgcmVjZW50IGltcGxlbWVudGF0aW9uIG9mIGBzZXVyYXRgLCBSYWh1bCBTYXRpamEgdG9vayBhIHNsaWdodGx5IGRpZmZlcmVudCBhcHByb2FjaCBmb3IgSFZHIGNhbGN1bGF0aW9uLiBUaGV5IGNhbGN1bGF0ZSB0aGUgdmFyaWFuY2UgYW5kIG1lYW4gZm9yIGVhY2ggZ2VuZSBpbiB0aGUgZGF0YXNldCAoc3RvcmluZyB0aGlzIGluIGBvYmplY3RAaHZnLmluZm9gKSwgYW5kIHNvcnRzIGdlbmVzIGJ5IHRoZWlyIHZhcmlhbmNlL21lYW4gcmF0aW8gKFZNUikuIFRoZXkgaGF2ZSBvYnNlcnZlZCB0aGF0IGZvciBsYXJnZS1jZWxsIGRhdGFzZXRzIHdpdGggdW5pcXVlIG1vbGVjdWxhciBpZGVudGlmaWVycywgc2VsZWN0aW5nIGhpZ2hseSB2YXJpYWJsZSBnZW5lcyAoSFZHKSBzaW1wbHkgYmFzZWQgb24gVk1SIGlzIGFuIGVmZmljaWVudCBhbmQgcm9idXN0IHN0cmF0ZWd5LiAKCkJ1dCBpbiB0aGUgaW1wbGVtZW50YXRpb24gdXNlZCBoZXJlIGEgdHJlbmQgaXMgZml0dGVkIHRvIHRoZSB2YXJpYW5jZSBlc3RpbWF0ZXMgb2YgdGhlIGVuZG9nZW5vdXMgZ2VuZXMsIHVzaW5nIHRoZSBgdXNlLnNwaWtlcz1GQUxTRWAgc2V0dGluZyBhcyBzaG93biBiZWxvdy4gVGhpcyBhc3N1bWVzIHRoYXQgdGhlIG1ham9yaXR5IG9mIGdlbmVzIGFyZSBub3QgdmFyaWFibHkgZXhwcmVzc2VkLCBzdWNoIHRoYXQgdGhlIHRlY2huaWNhbCBjb21wb25lbnQgZG9taW5hdGVzIHRoZSB0b3RhbCB2YXJpYW5jZSBmb3IgdGhvc2UgZ2VuZXMuIFRoZSBmaXR0ZWQgdmFsdWUgb2YgdGhlIHRyZW5kIGlzIHRoZW4gdXNlZCBhcyBhbiBlc3RpbWF0ZSBvZiB0aGUgdGVjaG5pY2FsIGNvbXBvbmVudC4gCgpfX3NwYW46X18gTG93LWFidW5kYW5jZSBnZW5lcyB3aXRoIG1lYW4gbG9nLWV4cHJlc3Npb24gYmVsb3cgYG1pbi5tZWFuYCBhcmUgbm90IHVzZWQgaW4gdHJlbmQgZml0dGluZywgdG8gcHJlc2VydmUgdGhlIHNlbnNpdGl2aXR5IG9mIHNwYW4tYmFzZWQgc21vb3RoZXJzIGF0IG1vZGVyYXRlLXRvLWhpZ2ggYWJ1bmRhbmNlcy4gSXQgYWxzbyBwcm90ZWN0cyBhZ2FpbnN0IGRpc2NyZXRlbmVzcywgd2hpY2ggY2FuIGludGVyZmVyZSB3aXRoIGVzdGltYXRpb24gb2YgdGhlIHZhcmlhYmlsaXR5IG9mIHRoZSB2YXJpYW5jZSBlc3RpbWF0ZXMgYW5kIGFjY3VyYXRlIHNjYWxpbmcgb2YgdGhlIHRyZW5kLiBUaGUgZGVmYXVsdCB0aHJlc2hvbGQgaXMgY2hvc2VuIGJhc2VkIG9uIHRoZSBwb2ludCBhdCB3aGljaCBkaXNjcmV0ZW5lc3MgaXMgb2JzZXJ2ZWQgaW4gdmFyaWFuY2UgZXN0aW1hdGVzIGZyb20gUG9pc3Nvbi1kaXN0cmlidXRlZCBjb3VudHMuIEZvciBoZXRlcm9nZW5lb3VzIGRyb3BsZXQgZGF0YSwgYSBsb3dlciB0aHJlc2hvbGQgb2YgMC4wMDEtMC4wMSBzaG91bGQgYmUgdXNlZC4KCgpgYGB7cn0KdmFyLmZpdCA8LSB0cmVuZFZhcihjZFNjRmlsdEFubm90LCBtZXRob2Q9ImxvZXNzIiwgdXNlLnNwaWtlcz1GQUxTRSwgbWluLm1lYW49MS4wKQp2YXIub3V0IDwtIGRlY29tcG9zZVZhcihjZFNjRmlsdEFubm90LCB2YXIuZml0KQp2YXIub3V0CmBgYAoKV2UgYXNzZXNzIHRoZSBzdWl0YWJpbGl0eSBvZiB0aGUgdHJlbmQgZml0dGVkIHRvIHRoZSBlbmRvZ2Vub3VzIHZhcmlhbmNlcyBieSBleGFtaW5pbmcgd2hldGhlciBpdCBpcyBjb25zaXN0ZW50IHdpdGggdGhlIHZhcmlhbmNlcy4gVGhlIHRyZW5kIHBhc3NlcyB0aHJvdWdoIG9yIGNsb3NlIHRvIG1vc3Qgb2YgdGhlIGVuZG9nZW5vdXMgZ2VuZSB2YXJpYW5jZXMsIGluZGljYXRpbmcgdGhhdCBvdXIgYXNzdW1wdGlvbiAodGhhdCBtb3N0IGdlbmVzIGhhdmUgbG93IGxldmVscyBvZiBiaW9sb2dpY2FsIHZhcmlhYmlsaXR5KSBpcyB2YWxpZC4gVGhpcyBzdHJhdGVneSBleHBsb2l0cyB0aGUgbGFyZ2UgbnVtYmVyIG9mIGVuZG9nZW5vdXMgZ2VuZXMgdG8gb2J0YWluIGEgc3RhYmxlIHRyZW5kLiAKCmBgYHtyfQpwbG90KHZhci5vdXQkbWVhbiwgdmFyLm91dCR0b3RhbCwgcGNoPTE2LCBjZXg9MC42LCB4bGFiPSJNZWFuIGxvZy1leHByZXNzaW9uIiwgeWxhYj0iVmFyaWFuY2Ugb2YgbG9nLWV4cHJlc3Npb24iKQpvIDwtIG9yZGVyKHZhci5vdXQkbWVhbikKbGluZXModmFyLm91dCRtZWFuW29dLCB2YXIub3V0JHRlY2hbb10sIGNvbD0iZG9kZ2VyYmx1ZSIsIGx3ZD0yKQpgYGAKSWRlYWxseSB0aGUgcGxvdCBhYm92ZSB3b3VsZCBsb29rIGxpa2UgdGhlIG1vdW50YWluLCB3aGVyZSBpdCB3b3VsZCBoYXZlIHJpc2UgaW4gdGhlIG1pZGRsZSBidXQgdGhlbiBkcm9wIG9mZiBhdCB0aGUgZW5kIChsb3cgdmFyaWFuY2UgZm9yIGhpZ2hseSBleHByZXNzZWQgZ2VuZXMpLiBUaGUgdHJlbmQgbGluZSB3b3VsZCBmb2xsb3cgaXQgYXMgd2VsbC4gCgpIVkcgYXJlIGRlZmluZWQgYXMgZ2VuZXMgd2l0aCBiaW9sb2dpY2FsIGNvbXBvbmVudHMgdGhhdCBhcmUgc2lnbmlmaWNhbnRseSBncmVhdGVyIHRoYW4gemVybyBhdCBhIGZhbHNlIGRpc2NvdmVyeSByYXRlIChGRFIpIG9mIDUlLiBUaGVzZSBnZW5lcyBhcmUgaW50ZXJlc3RpbmcgYXMgdGhleSBkcml2ZSBkaWZmZXJlbmNlcyBpbiB0aGUgZXhwcmVzc2lvbiBwcm9maWxlcyBiZXR3ZWVuIGNlbGxzLCBhbmQgc2hvdWxkIGJlIHByaW9yaXRpemVkIGZvciBmdXJ0aGVyIGludmVzdGlnYXRpb24uIEhlcmUgYSBnZW5lIGlzIGNvbnNpZGVyZWQgdG8gYmUgYSBIVkcgaWYgaXQgaGFzIGEgYmlvbG9naWNhbCBjb21wb25lbnQgZ3JlYXRlciB0aGFuIG9yIGVxdWFsIHRvIDAuNS4gRm9yIHRyYW5zZm9ybWVkIGV4cHJlc3Npb24gdmFsdWVzIG9uIHRoZSBsb2cyIHNjYWxlLCB0aGlzIG1lYW5zIHRoYXQgdGhlIGF2ZXJhZ2UgZGlmZmVyZW5jZSBpbiB0cnVlIGV4cHJlc3Npb24gYmV0d2VlbiBhbnkgdHdvIGNlbGxzIHdpbGwgYmUgYXQgbGVhc3QgMi1mb2xkLiAoVGhpcyByZWFzb25pbmcgYXNzdW1lcyB0aGF0IHRoZSB0cnVlIGxvZy1leHByZXNzaW9uIHZhbHVlcyBhcmUgTm9ybWFsbHkgZGlzdHJpYnV0ZWQgd2l0aCB2YXJpYW5jZSBvZiAwLjUuIFRoZSByb290LW1lYW4tc3F1YXJlIG9mIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdHdvIHZhbHVlcyBpcyB0cmVhdGVkIGFzIHRoZSBhdmVyYWdlIGxvZzItZm9sZCBjaGFuZ2UgYmV0d2VlbiBjZWxscyBhbmQgaXMgZXF1YWwgdG8gdW5pdHkuKSBUaGUgcmVzdWx0cyBhcmUgcmFua2VkIGJ5IHRoZSBiaW9sb2dpY2FsIGNvbXBvbmVudCB0byBmb2N1cyBvbiBnZW5lcyB3aXRoIGxhcmdlciBiaW9sb2dpY2FsIHZhcmlhYmlsaXR5LiBIVkcgY2FuIG51bWJlciBhbnl3aGVyZSBiZXR3ZWVuIDIwMC01MDAwIGRlcGVuZGluZyBvbiB0aGUgY29tcGxleGl0eSBvZiB0aGUgZGF0YXNldCBhbmQgZGVwdGggb2Ygc2VxdWVuY2luZyBodHRwczovL3d3dy5lbWJvcHJlc3Mub3JnL2RvaS8xMC4xNTI1Mi9tc2IuMjAxODg3NDYKCgpgYGB7cn0KaHZnLm91dCA8LSB2YXIub3V0W3doaWNoKHZhci5vdXQkRkRSIDw9IDAuMDUgJiB2YXIub3V0JGJpbyA+PSAwLjUpLF0KaHZnLm91dCA8LSBodmcub3V0W29yZGVyKGh2Zy5vdXQkYmlvLCBkZWNyZWFzaW5nPVRSVUUpLF0KcHJpbnQocGFzdGUwKCdOdW1iZXIgb2YgSFZHIHVzaW5nIHRoaXMgc2V0dGluZyBpcyAnICxhcy5jaGFyYWN0ZXIobnJvdyhodmcub3V0KSkpKQpgYGAKCmBgYHtyfQpoZWFkKHJvd25hbWVzKGh2Zy5vdXQpLDIwKQpgYGAKCgpQbG90dGluZyB0aGUgSFZHcy4gVGhlIHJlZCBkb3RzIGFyZSB0aGUgSFZHcyB3ZSBzZWxlY3RlZC4gCgpgYGB7cn0KcGxvdCh2YXIub3V0JG1lYW4sIHZhci5vdXQkdG90YWwsIHBjaD0xNiwgY2V4PTAuNiwgeGxhYj0iTWVhbiBsb2ctZXhwcmVzc2lvbiIsCiAgICAgeWxhYj0iVmFyaWFuY2Ugb2YgbG9nLWV4cHJlc3Npb24iKQpvIDwtIG9yZGVyKHZhci5vdXQkbWVhbikKbGluZXModmFyLm91dCRtZWFuW29dLCB2YXIub3V0JHRlY2hbb10sIGNvbD0iZG9kZ2VyYmx1ZSIsIGx3ZD0yKQpwb2ludHModmFyLm91dFtyb3duYW1lcyhodmcub3V0KSxdJG1lYW4sIHZhci5vdXRbcm93bmFtZXMoaHZnLm91dCksXSR0b3RhbCwgY29sPSJyZWQiLCBwY2g9MTYsIGNleD0wLjYpCmBgYAoKUGxvdHRpbmcgdGhlIHRvcCAxNSBIVkdzOgpgYGB7ciBmaWdFeHAsIGZpZy53aWR0aD05fQpwbG90RXhwcmVzc2lvbihjZFNjRmlsdEFubm90LCByb3duYW1lcyhodmcub3V0KVsxOjE1XSkKYGBgCgpfX0Rpc2N1c3MgbWVhbmluZyBvZiBnZW5lcy5fXwoKCmBgYHtyfQp3cml0ZS50YWJsZShmaWxlPSJIVkcuY3N2IiwgaHZnLm91dCwgc2VwPSJcdCIsIHF1b3RlID0gRkFMU0UsIGNvbC5uYW1lcyA9IE5BKQpoZWFkKGh2Zy5vdXQsIDIwKQpgYGAKCl9fRmluYWwgTm90ZXNfXyBUaGVyZSBhcmUgYWx0ZXJuYXRpdmUgYXBwcm9hY2hlcyBmb3IgZGV0ZXJtaW5pbmcgdGhlIEhWRywgc3NwZWNpYWxseSB0aG9zZSBiYXNlZCBvbiBDb2VmZmljaWVudCBvZiBWYXJpYW5jZS4gVGhlIG1ldGhvZCB1c2VkIGhlcmUsIHRoZSB2YXJpYW5jZSBvZiB0aGUgbG9nLWV4cHJlc3Npb24gdmFsdWVzLCBhdm9pZHMgZ2VuZXMgd2l0aCBzdHJvbmcgZXhwcmVzc2lvbiBpbiBvbmx5IG9uZSBvciB0d28gY2VsbHMuIFRoaXMgZW5zdXJlcyB0aGF0IHRoZSBzZXQgb2YgdG9wIEhWR3MgaXMgbm90IGRvbWluYXRlZCBieSBnZW5lcyB3aXRoIChtb3N0bHkgdW5pbnRlcmVzdGluZykgb3V0bGllciBleHByZXNzaW9uIHBhdHRlcm5zLgoKSG93ZXZlciwgaXQgaGFzIGJlZW4gbWVudGlvbmVkIHRoYXQgZml0dGluZyB0aGUgdHJlbmRsaW5lIHRvIGVuZG9nZW5vdXMgZ2VuZXMgbWlnaHQgbm90IGFsd2F5cyBiZSBhIGdvb2QgaWRlYS4KCiMgOC4gQW5hbHlzaXMgb2YgdGhlIGRhdGFzZXQgdXNpbmcgaGlnaGx5IHZhcmlhYmxlIGdlbmVzIEhWRzxhIG5hbWU9J3NlY3Rpb244JyAvPgogICAgMS4gUENBCiAgICAyLiB0LVNORQogICAgMy4gVU1BUAoKYGBge3IgbXVsdGlQQ0FIVkcsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMn0Kcm93VmFyc1NvcnRlZCA8LSBleHBycyhjZFNjRmlsdEFubm90KVtyb3duYW1lcyhodmcub3V0KSxdCkZpbmFsUENBRGF0YSA8LSB0KHJvd1ZhcnNTb3J0ZWQpCnBjYVBSQ29tcCA8LSBwcmNvbXAoRmluYWxQQ0FEYXRhKQpubWF4ID0gMTAKaWYoZGltKHBjYVBSQ29tcCR4KVsxXSA8IDEwKXsgbm1heCA9IGRpbShwY2FQUkNvbXAkeClbMV0gfQp0eHQxIDwtIHBhc3RlKCJQZXJjZW50X1BDX1Zhcl9vbmZpcnN0IixubWF4LCJQQ3MiLHNlcD0iIikKcGNhX3ZhciA9IHBjYVBSQ29tcCRzZGV2IF4gMgpwY2FfdmFyX3BlcmNlbnQgPC0gMTAwICogcGNhX3ZhciAvIHN1bShwY2FfdmFyKQpwY2FfdmFyX3BlcmNlbnRfZmlyc3QxMCA8LSBOQSAqIHBjYV92YXIKcGNhX3Zhcl9wZXJjZW50X2ZpcnN0MTBbMTpubWF4XSA8LSAxMDAgKiBwY2FfdmFyWzE6bm1heF0gLyBzdW0ocGNhX3ZhclsxOm5tYXhdKQoKI3BjYV9jb3JyX3JlYWRzIDwtIGFwcGx5KHBjYVBSQ29tcCR4LDIsZnVuY3Rpb24oeCkgY29yKHgscmVwb3J0X3N1YiRBc3NpZ25lZCkpCiAgICAKcGNhX3Zhcl9vdXQgPC0gZGF0YS5mcmFtZShyb3VuZChwY2FfdmFyLDMpLHJvdW5kKHBjYV92YXJfcGVyY2VudCwxKSwKICAgICAgICAgICAgICAgICAgICAgICAgICByb3VuZChwY2FfdmFyX3BlcmNlbnRfZmlyc3QxMCwxKSkKI3Jvd25hbWVzKHBjYV92YXJfb3V0KSA8LSByb3duYW1lcyhwY2FQUkNvbXAkeCkKY29sbmFtZXMocGNhX3Zhcl9vdXQpIDwtIGMoIlBDX1ZhciIsIlBDX1Zhcl9wZXJjZW50Iix0eHQxKQpuQ29sVG9EaXNwbGF5ID0gNQpkZiA8LSBhcy5kYXRhLmZyYW1lKHBjYVBSQ29tcCR4KQpkZiRDZWxsPWFzLmZhY3Rvcihjb2xEYXRhKGNkU2NGaWx0QW5ub3QpJFNhbXBsZSkKcCA8LSBnZ3BhaXJzKGRmLCBjb2x1bW5zPTE6bkNvbFRvRGlzcGxheSwgdXBwZXI9bGlzdChjb250aW51b3VzPSJwb2ludHMiKSwgCiAgICAgICAgICAgICB0aXRsZT0nUGxvdHRpbmcgZmlyc3QgZm91ciBQQ0FzJywgCiAgICAgICAgICAgICBtYXBwaW5nID0gYWVzX3N0cmluZyhjb2xvcj0iQ2VsbCIpLAogICAgICAgICAgICAgbGVnZW5kID0gYygxLG5Db2xUb0Rpc3BsYXkpLAogICAgICAgICAgICAgY29sdW1uTGFiZWxzID0gYXMuY2hhcmFjdGVyKHBhc3RlMChjb2xuYW1lcyhkZlssMTpuQ29sVG9EaXNwbGF5XSksICcgOiAnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGNhX3Zhcl9vdXQkUENfVmFyX3BlcmNlbnRbMTpuQ29sVG9EaXNwbGF5XSwgJyUgdmFyaWFuY2UnKSkpKwogICAgdGhlbWVfbGlnaHQoYmFzZV9zaXplPTE1KSsgICAgIAogICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCgpmb3IoaSBpbiAxOnAkbnJvdykgewogIGZvcihqIGluIDE6cCRuY29sKXsKICAgIHBbaSxqXSA8LSBwW2ksal0gKyAKICAgICAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9Y19zYW1wbGVfY29sKSArCiAgICAgICAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9Y19zYW1wbGVfY29sKSAgCiAgfQp9CiAgICAKcHJpbnQocCkKYGBgCmBgYHtyIFR3b1BDQUhWRywgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9Nn0KI1BDIDF2MgpkZl9vdXQgPC0gYXMuZGF0YS5mcmFtZShwY2FQUkNvbXAkeCkKZGZfb3V0JGdyb3VwIDwtIHNhcHBseSggc3Ryc3BsaXQoYXMuY2hhcmFjdGVyKHJvdy5uYW1lcyhkZikpLCAiXyIpLCAiW1siLCAxICkKU2FtcGxlcz1hcy5mYWN0b3IoY29sRGF0YShjZFNjRmlsdEFubm90KSRTYW1wbGUpCnBwMTI8LWdncGxvdChkZl9vdXQsYWVzKHg9UEMxLHk9UEMyLGNvbG9yPVNhbXBsZXMgKSkKcHAxMjwtcHAxMitnZW9tX3BvaW50KCkrIAogICAgICAgIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWNfc2FtcGxlX2NvbCkrCiAgICB4bGFiKHBhc3RlMCgiUEMxOiAiLHJvdW5kKHBjYV92YXJfcGVyY2VudF9maXJzdDEwWzFdLDEpLCIlIHZhcmlhbmNlIikpICsKICAgIHlsYWIocGFzdGUwKCJQQzI6ICIscm91bmQocGNhX3Zhcl9wZXJjZW50X2ZpcnN0MTBbMl0sMSksIiUgdmFyaWFuY2UiKSkgKyB0aGVtZV9jbGFzc2ljKCkKI3BjYV92YXJfcGVyY2VudF9maXJzdDEwWzFdIDwtIDEwMCAqIHBjYV92YXJbMTpubWF4XSAvIHN1bShwY2FfdmFyWzE6bm1heF0pCgojUEMgM3Y0CmRmX291dCA8LSBhcy5kYXRhLmZyYW1lKHBjYVBSQ29tcCR4KQpkZl9vdXQkZ3JvdXAgPC0gc2FwcGx5KCBzdHJzcGxpdChhcy5jaGFyYWN0ZXIocm93Lm5hbWVzKGRmKSksICJfIiksICJbWyIsIDEgKQpTYW1wbGVzPWFzLmZhY3Rvcihjb2xEYXRhKGNkU2NGaWx0QW5ub3QpJFNhbXBsZSkKcHAzNDwtZ2dwbG90KGRmX291dCxhZXMoeD1QQzMseT1QQzQsY29sb3I9U2FtcGxlcyApKQpwcDM0PC1wcDM0K2dlb21fcG9pbnQoKSsgCiAgICAgICAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9Y19zYW1wbGVfY29sKSsKICAgIHhsYWIocGFzdGUwKCJQQzM6ICIscm91bmQocGNhX3Zhcl9wZXJjZW50X2ZpcnN0MTBbM10sMSksIiUgdmFyaWFuY2UiKSkgKwogICAgeWxhYihwYXN0ZTAoIlBDNDogIixyb3VuZChwY2FfdmFyX3BlcmNlbnRfZmlyc3QxMFs0XSwxKSwiJSB2YXJpYW5jZSIpKSArIHRoZW1lX2NsYXNzaWMoKQoKbXVsdGlwbG90KHBwMTIscHAzNCwgY29scz0xKQpgYGAKCgoKIyMjIHQtU05FIHBsb3Qgd2l0aCBvbmx5IHRoZSBIVkcgZ2VuZXM6CldlIGZpcnN0IGNhbGN1bGF0ZSBob3cgbXVjaCBvZiB2YXJpYW5jZSBlYWNoIFBDIGlzIGdpdmluZwoKYGBge3J9CnBsb3QoYygxOjIwKSwgcGNhX3Zhcl9wZXJjZW50WzE6MjBdKQpgYGAKCmBgYHtyfQogcGNhX3Zhcl9wZXJjZW50WzE6MjBdCmBgYAoKPGZvbnQgY29sb3I9cmVkPl9fTm90ZSB0LVNORSBjYWxjdWxhdGVkIG5leHQuIEV2ZXJ5dGltZSB5b3UgcnVuIHRoaXMgc3RlcCB0aGUgdC1TTkUgcGxvdCB3aWxsIGxvb2sgZGlmZmVyZW50Ll9fPC9mb250PgoKCmBgYHtyfQp0c25lX291dCA8LSBSdHNuZShhcy5tYXRyaXgocGNhUFJDb21wJHhbLDE6MTBdKSxjaGVja19kdXBsaWNhdGVzID0gRkFMU0UsIHBjYSA9IEZBTFNFLAogICAgICAgICAgICAgcGVycGxleGl0eT0zMCwgdGhldGE9MC4wMSwgZGltcz0yLCBudW1fdGhyZWFkcyA9IDgpCgpSZXAgPC0gYXMuZmFjdG9yKGNvbERhdGEoY2RTY0ZpbHRBbm5vdCkkU2FtcGxlKQoKCmNvdW50cyA8LSBjb2xEYXRhKGNkU2NGaWx0QW5ub3QpJHRvdGFsX2NvdW50cwpgYGAKCgpgYGB7ciBmaWdUc25lTTNEcm9wR2VuZXMsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTZ9CnAgPC0gZ2dwbG90KGFzLmRhdGEuZnJhbWUodHNuZV9vdXQkWSksIGFlcyh4PVYxLCB5PVYyLCBjb2xvcj1jb3VudHMpKSArCiAgICAgZ2VvbV9wb2ludChzaXplPTAuNzUpICsKICAgICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0wLjUpKSkgKwogICAgIHhsYWIoIiIpICsgeWxhYigiIikgKwogICAgIGdndGl0bGUoInQtU05FIDJEIEVtYmVkZGluZyBvZiBTYW1wbGUgRGF0YSIpICsKICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZT0xNCkgKwogICAgIHRoZW1lKHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgc3RyaXAudGV4dC54ICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBheGlzLnRleHQueCAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIGF4aXMudGV4dC55ICAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgYXhpcy50aWNrcyAgICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBheGlzLmxpbmUgICAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIHBhbmVsLmJvcmRlciAgICAgPSBlbGVtZW50X2JsYW5rKCkpCgpwMiA8LSBnZ3Bsb3QoYXMuZGF0YS5mcmFtZSh0c25lX291dCRZKSwgYWVzKHg9VjEsIHk9VjIsIGNvbG9yPVJlcCkpICsKICAgICBnZW9tX3BvaW50KHNpemU9MC43NSkgKwogICAgIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTAuOCkpKSArCiAgICAgeGxhYigiIikgKyB5bGFiKCIiKSArCiAgICAgZ2d0aXRsZSgidC1TTkUgMkQgRW1iZWRkaW5nIG9mIEV4cHJlc3Npb24gRGF0YSIpICsKICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZT0xMCkgKwogICAgIHRoZW1lKHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgc3RyaXAudGV4dC54ICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBheGlzLnRleHQueCAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIGF4aXMudGV4dC55ICAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgYXhpcy50aWNrcyAgICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBheGlzLmxpbmUgICAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIHBhbmVsLmJvcmRlciAgICAgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jX3NhbXBsZV9jb2wpICsKICAgICAgICAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9Y19zYW1wbGVfY29sKQpwMgoKI211bHRpcGxvdChwLHAyLGNvbHM9MikKYGBgCmBgYHtyfQpyZWR1Y2VkRGltKGNkU2NGaWx0QW5ub3QsJ3RTTkUnKSA8LSB0c25lX291dCRZCmBgYAoKCgojIyMgUnVubmluZyBVTUFQIGZvciBkYXRhIHZpc3VhbGl6YXRpb24KYGBge3J9CmVtYmVkZGluZyA8LSB1bWFwKHBjYVBSQ29tcCR4WywxOjEwXSkKYXMudGliYmxlKGVtYmVkZGluZyRsYXlvdXQpICU+JQogIG11dGF0ZShTYW1wbGVzID0gY29sRGF0YShjZFNjRmlsdEFubm90KSRTYW1wbGUpICU+JQogIGdncGxvdChhZXMoVjEsIFYyLCBjb2xvcj1TYW1wbGVzKSkgKyAgZ2VvbV9wb2ludChzaXplPTAuNzUpICsgdGhlbWVfY2xhc3NpYygpICtzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1jX3NhbXBsZV9jb2wpCmBgYApgYGB7cn0KcmVkdWNlZERpbShjZFNjRmlsdEFubm90LCdVTUFQJykgPC0gZW1iZWRkaW5nJGxheW91dApgYGAKCgpgYGB7cn0Kc2F2ZS5pbWFnZSgpCmBgYAoKIyA5LiBDbHVzdGVyaW5nIGNlbGxzClRoZSBnb2FsIGlzIHRvIHNwbGl0IHRoZSBjZWxscyBpbiB0aGUgZGF0YXNldCBpbnRvIGNsdXN0ZXJzLCBzdWNoIHRoYXQ6CjEuIHRoZSBjZWxscyBpbiB0aGUgc2FtZSBjbHVzdGVyIGFyZSBhcyBzaW1pbGFyIGFzIHBvc3NpYmxlLAoyLiB0aGUgY2VsbHMgaW4gZGlmZmVyZW50IGNsdXN0ZXJzIGFyZSBoaWdobHkgZGlzdGluY3QKClRoZXJlIGFyZSBudW1lcm91cyBtZXRob2RzIG9mIGNsdXN0ZXJpbmc7IGR5bmFtaWMtY3V0LXRyZWUsIGstbWVhbnMsIGstbWVkb2lkcywgR01NLCBHUC1MVk0gYW5kIHNvIG9uLiBIZXJlIHRoZSBkeW5hbWljLWN1dC10cmVlIHdpbGwgYmUgdXNlZC4KCiMjIyBEeW5hbWljIEN1dCBUcmVlIERlc2NyaXB0aW9uCkhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIGlzIGEgd2lkZWx5IHVzZWQgbWV0aG9kIGZvciBkZXRlY3RpbmcgY2x1c3RlcnMgaW4gZ2Vub21pYyBkYXRhLiBDbHVzdGVycyBhcmUgZGVmaW5lZCBieSBjdXR0aW5nIGJyYW5jaGVzIG9mZiB0aGUgZGVuZHJvZ3JhbS4gQSBjb21tb24gYnV0IGluZmxleGlibGUgbWV0aG9kIHVzZXMgYSBjb25zdGFudCBoZWlnaHQgY3V0b2ZmIHZhbHVlOyB0aGlzIG1ldGhvZCBleGhpYml0cyBzdWJvcHRpbWFsIHBlcmZvcm1hbmNlIG9uIGNvbXBsaWNhdGVkIGRlbmRyb2dyYW1zLiBUaGUgRHluYW1pYyBUcmVlIEN1dCBSIGxpYnJhcnkgaW1wbGVtZW50cyBub3ZlbCBkeW5hbWljIGJyYW5jaCBjdXR0aW5nIG1ldGhvZHMgZm9yIGRldGVjdGluZyBjbHVzdGVycyBpbiBhIGRlbmRyb2dyYW0gZGVwZW5kaW5nIG9uIHRoZWlyIHNoYXBlLiBDb21wYXJlZCB0byB0aGUgY29uc3RhbnQgaGVpZ2h0IGN1dG9mZiBtZXRob2QsIHRoZXNlIHRlY2huaXF1ZXMgb2ZmZXIgdGhlIGZvbGxvd2luZyBhZHZhbnRhZ2VzOiAoMSkgdGhleSBhcmUgY2FwYWJsZSBvZiBpZGVudGlmeWluZyBuZXN0ZWQgY2x1c3RlcnM7ICgyKSB0aGV5IGFyZSBmbGV4aWJsZSAtLS0gY2x1c3RlciBzaGFwZSBwYXJhbWV0ZXJzIGNhbiBiZSB0dW5lZCB0byBzdWl0IHRoZSBhcHBsaWNhdGlvbiBhdCBoYW5kOyAoMykgdGhleSBhcmUgc3VpdGFibGUgZm9yIGF1dG9tYXRpb247IGFuZCAoNCkgdGhleSBjYW4gb3B0aW9uYWxseSBjb21iaW5lIHRoZSBhZHZhbnRhZ2VzIG9mIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIGFuZCBwYXJ0aXRpb25pbmcgYXJvdW5kIG1lZG9pZHMsIGdpdmluZyBiZXR0ZXIgZGV0ZWN0aW9uIG9mIG91dGxpZXJzLgoKIyMjIENsdXN0ZXJpbmcgY2VsbHMgaW50byBwdXRhdGl2ZSBzdWJwb3B1bGF0aW9ucwpIaWVyYXJjaGljYWwgY2x1c3RlcmluZyBpcyBwZXJmb3JtZWQgb24gdGhlIEV1Y2xpZGVhbiBkaXN0YW5jZXMgYmV0d2VlbiBjZWxscywgdXNpbmcgV2FyZOKAmXMgY3JpdGVyaW9uIHRvIG1pbmltaXplIHRoZSB0b3RhbCB2YXJpYW5jZSB3aXRoaW4gZWFjaCBjbHVzdGVyLiBUaGlzIHlpZWxkcyBhIGRlbmRyb2dyYW0gdGhhdCBncm91cHMgdG9nZXRoZXIgY2VsbHMgd2l0aCBzaW1pbGFyIGV4cHJlc3Npb24gcGF0dGVybnMgYWNyb3NzIHRoZSBjaG9zZW4gZ2VuZXMuIAoKQ2x1c3RlcnMgYXJlIGV4cGxpY2l0bHkgZGVmaW5lZCBieSBhcHBseWluZyBhIGR5bmFtaWMgdHJlZSBjdXQgKExhbmdmZWxkZXIgZXQgYWwuLCAyMDA4KVtodHRwczovL2FjYWRlbWljLm91cC5jb20vYmlvaW5mb3JtYXRpY3MvYXJ0aWNsZS8yNC81LzcxOS8yMDA3NTFdIHRvIHRoZSBkZW5kcm9ncmFtLiBUaGlzIGV4cGxvaXRzIHRoZSBzaGFwZSBvZiB0aGUgYnJhbmNoZXMgaW4gdGhlIGRlbmRyb2dyYW0gdG8gcmVmaW5lIHRoZSBjbHVzdGVyIGRlZmluaXRpb25zLCBhbmQgaXMgbW9yZSBhcHByb3ByaWF0ZSB0aGFuIGN1dHJlZSBmb3IgY29tcGxleCBkZW5kcm9ncmFtcy4gR3JlYXRlciBjb250cm9sIG9mIHRoZSBlbXBpcmljYWwgY2x1c3RlcnMgY2FuIGJlIG9idGFpbmVkIGJ5IG1hbnVhbGx5IHNwZWNpZnlpbmcgY3V0SGVpZ2h0IGluIGN1dHJlZUR5bmFtaWMuCgpBbiBhbHRlcm5hdGl2ZSBhcHByb2FjaCBpcyB0byBjbHVzdGVyIG9uIGEgbWF0cml4IG9mIGRpc3RhbmNlcyBkZXJpdmVkIGZyb20gY29ycmVsYXRpb25zIChlLmcuLCBhcyBpbiBgcXVpY2tDbHVzdGVyYCkuIFRoaXMgaXMgbW9yZSByb2J1c3QgdG8gbm9pc2UgYW5kIG5vcm1hbGl6YXRpb24gZXJyb3JzLCBidXQgaXMgYWxzbyBsZXNzIHNlbnNpdGl2ZSB0byBzdWJ0bGUgY2hhbmdlcyBpbiB0aGUgZXhwcmVzc2lvbiBwcm9maWxlcy4KCmBgYHtyfQpjaG9zZW4uZXhwcnMgPC0gbG9nY291bnRzKGNkU2NGaWx0QW5ub3Rbcm93bmFtZXMoaHZnLm91dCksXSkKbXkuZGlzdCA8LSBkaXN0KHQoY2hvc2VuLmV4cHJzKSkKbXkudHJlZSA8LSBoY2x1c3QobXkuZGlzdCwgbWV0aG9kID0gIndhcmQuRDIiKQpteS5jbHVzdGVycyA8LSB1bm5hbWUoY3V0cmVlRHluYW1pYyhteS50cmVlLCBkaXN0TT1hcy5tYXRyaXgobXkuZGlzdCksIHZlcmJvc2U9MCkpCnByaW50KHBhc3RlMCgnTnVtYmVyIG9mIGNsdXN0ZXJzJykpCmxldmVscyhhcy5mYWN0b3IobXkuY2x1c3RlcnMpKQpgYGAKCmBgYHtyfQpjZFNjRmlsdEFubm90JENsdXN0ZXJzIDwtIGFzLmZhY3RvcihteS5jbHVzdGVycykKYGBgCgpEcmF3aW5nIHRoZSBoZWF0bWFwLCBmaXJzdCBzY2FsZSB0ZTogCmBgYHtyIGZpZ0hlYXRDbHVzdCwgZmlnLmhlaWdodD0xMiwgZmlnLndpZHRoPTh9CmhlYXQudmFscyA8LSBjaG9zZW4uZXhwcnMgLSByb3dNZWFucyhjaG9zZW4uZXhwcnMpCgojY2x1c3QuY29sIDwtIHJhaW5ib3cobWF4KG15LmNsdXN0ZXJzKSkKI2hlYXRtYXAuMihoZWF0LnZhbHMsIGNvbD1ibHVlcmVkLCBzeW1icmVhaz1UUlVFLCB0cmFjZT0nbm9uZScsIGNleFJvdz0wLjMsCiMgICAgQ29sU2lkZUNvbG9ycz1jbHVzdC5jb2xbbXkuY2x1c3RlcnNdLCBDb2x2PWFzLmRlbmRyb2dyYW0obXkudHJlZSkpCgpkZiA9IGRhdGEuZnJhbWUoQ2xhc3MgPSBhcy5mYWN0b3IobXkuY2x1c3RlcnMpKQpoYSA9IEhlYXRtYXBBbm5vdGF0aW9uKGRmID0gZGYpCiNoYSA9IEhlYXRtYXBBbm5vdGF0aW9uKGRmID0gZGYsIGNvbCA9IGxpc3QoQ29uZGl0aW9uID0gYygiTUNfQSIgPSAgImRvZGdlcmJsdWUiLCAiTUNfQiI9ICJmaXJlYnJpY2siLCAiTUNfQyI9ImZvcmVzdGdyZWVuIiwgIk1DX0QiPSJnb2xkIikpKQpIZWF0bWFwKGhlYXQudmFscyAsIHRvcF9hbm5vdGF0aW9uID0gaGEsIHNob3dfY29sdW1uX25hbWVzPUZBTFNFLCBjbHVzdGVyX3Jvd3MgPSBUUlVFLCBjbHVzdGVyaW5nX21ldGhvZF9jb2x1bW5zID0gIndhcmQuRDIiLCByb3dfbmFtZXNfZ3AgPSBncGFyKGZvbnRzaXplID0gOCkpCmBgYAoKU2F2aW5nIHRoZSBoZWF0bWFwIGZvciBsYXRlciBleHBsb3JhdGlvbjoKYGBge3J9CnBkZigiaGVhdG1hcC5wZGYiLCB3aWR0aD0yMCwgaGVpZ2h0PTQwKQpIZWF0bWFwKGhlYXQudmFscyAsIHRvcF9hbm5vdGF0aW9uID0gaGEsIHNob3dfY29sdW1uX25hbWVzPUZBTFNFLCBjbHVzdGVyX3Jvd3MgPSBUUlVFLCBjbHVzdGVyaW5nX21ldGhvZF9jb2x1bW5zID0gIndhcmQuRDIiLCByb3dfbmFtZXNfZ3AgPSBncGFyKGZvbnRzaXplID0gOCkpCmRldi5vZmYoKQpgYGAKCnQtU05FIHdpdGggY2x1c3RlciBhbGxvY2F0aW9uOgpgYGB7ciBmaWd0c25lQ2x1c3RlckFsbG9jLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD02fQojIyB0LVNORSB3aXRoIGNsdXN0ZXIgYWxsb2NhdGlvbgpDbHVzdGVycyA8LSBhcy5mYWN0b3IobXkuY2x1c3RlcnMpCgpwMSA8LSBnZ3Bsb3QoYXMuZGF0YS5mcmFtZSh0c25lX291dCRZKSwgYWVzKHg9VjEsIHk9VjIsIGNvbG9yPUNsdXN0ZXJzKSkgKwogICAgIGdlb21fcG9pbnQoc2l6ZT0xLjApICsKICAgICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0xKSkpICsKICAgICB4bGFiKCIiKSArIHlsYWIoIiIpICsKICAgICBnZ3RpdGxlKCJ0LVNORSAyRCBjb2xvdXJlZCBieSBjbHVzdGVycyIpICsKICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZT0xMCkgKwogICAgIHRoZW1lKHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgc3RyaXAudGV4dC54ICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBheGlzLnRleHQueCAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIGF4aXMudGV4dC55ICAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgYXhpcy50aWNrcyAgICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBheGlzLmxpbmUgICAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIHBhbmVsLmJvcmRlciAgICAgPSBlbGVtZW50X2JsYW5rKCkpICsgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jX2NsdXN0X2NvbCkKcDEKCiNtdWx0aXBsb3QocCxwMixjb2xzPTIpCmBgYAoKCmBgYHtyIGZpZ3RzbmVDZWxsLWN5Y2xlQWxsb2MsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTZ9CiMjIHQtU05FIHdpdGggQ2VsbC1jeWNsZQpDZWxsQ3ljbGUgPC0gYXMuZmFjdG9yKGNkU2NGaWx0QW5ub3QkQ2VsbEN5Y2xlKQoKcDIgPC0gZ2dwbG90KGFzLmRhdGEuZnJhbWUodHNuZV9vdXQkWSksIGFlcyh4PVYxLCB5PVYyLCBjb2xvcj1DZWxsQ3ljbGUpKSArCiAgICAgZ2VvbV9wb2ludChzaXplPTEuMCkgKwogICAgIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTEpKSkgKwogICAgIHhsYWIoIiIpICsgeWxhYigiIikgKwogICAgIGdndGl0bGUoInQtU05FIDJEIGNvbG91cmVkIGJ5IGNlbGwtY3ljbGUiKSArCiAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemU9MTApICsKICAgICB0aGVtZShzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIHN0cmlwLnRleHQueCAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgYXhpcy50ZXh0LnggICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBheGlzLnRleHQueSAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIGF4aXMudGlja3MgICAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgYXhpcy5saW5lICAgICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBwYW5lbC5ib3JkZXIgICAgID0gZWxlbWVudF9ibGFuaygpKSAKcDIKI211bHRpcGxvdChwLHAyLGNvbHM9MikKYGBgCgpgYGB7ciBmaWd0c25lU2FtcGxlQWdhaW5BbGxvYywgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9Nn0KIyMgdC1TTkUgd2l0aCBTYW1wbGUKU2FtcGxlIDwtIGFzLmZhY3RvcihjZFNjRmlsdEFubm90JFNhbXBsZSkKCnAzIDwtIGdncGxvdChhcy5kYXRhLmZyYW1lKHRzbmVfb3V0JFkpLCBhZXMoeD1WMSwgeT1WMiwgY29sb3I9U2FtcGxlKSkgKwogICAgIGdlb21fcG9pbnQoc2l6ZT0xLjApICsKICAgICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0xKSkpICsKICAgICB4bGFiKCIiKSArIHlsYWIoIiIpICsKICAgICBnZ3RpdGxlKCJ0LVNORSAyRCBjb2xvdXJlZCBieSBzYW1wbGUiKSArCiAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemU9MTApICsKICAgICB0aGVtZShzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIHN0cmlwLnRleHQueCAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgYXhpcy50ZXh0LnggICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBheGlzLnRleHQueSAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIGF4aXMudGlja3MgICAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgYXhpcy5saW5lICAgICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBwYW5lbC5ib3JkZXIgICAgID0gZWxlbWVudF9ibGFuaygpKSAKcDMKYGBgCgoKYGBge3IgZmlndHNuZVJlYWRDb3VudEFsbG9jLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD02fQojIyB0LVNORSBDbHVzdGVyaW5nIGltcGFjdGVkIHdpdGggcmVhZC1jb3VudHMKcDQgPC0gYXMudGliYmxlKGFzLmRhdGEuZnJhbWUodHNuZV9vdXQkWSkpICU+JQogIG11dGF0ZShSZWFkRGVwdGggPSBjZFNjRmlsdEFubm90JGxvZzEwX3RvdGFsX2NvdW50cykgJT4lCiAgZ2dwbG90KGFlcyhWMSwgVjIsIGNvbG9yPVJlYWREZXB0aCkpICsgIGdlb21fcG9pbnQoc2l6ZT0wLjc1KSArIHRoZW1lX2NsYXNzaWMoKSArCiAgc2NhbGVfY29sb3VyX2dyYWRpZW50bihjb2xvdXJzID0gcmFpbmJvdyg1KSkgKyAKICB0aGVtZShzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIHN0cmlwLnRleHQueCAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgYXhpcy50ZXh0LnggICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBheGlzLnRleHQueSAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIGF4aXMudGlja3MgICAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgYXhpcy5saW5lICAgICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBwYW5lbC5ib3JkZXIgICAgID0gZWxlbWVudF9ibGFuaygpKSAKcDQKYGBgCmBgYHtyIGZpZ3RzbmVBbGx0QWxsb2MsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTh9Cm11bHRpcGxvdChwMSxwMixwMyxwNCwgY29scz0yKQpgYGAKCgpgYGB7cn0KQ2x1c3RlcnMgPC0gYXMuZmFjdG9yKG15LmNsdXN0ZXJzKQphcy50aWJibGUoZW1iZWRkaW5nJGxheW91dCkgJT4lCiAgbXV0YXRlKENsdXN0ZXJzID0gQ2x1c3RlcnMpICU+JQogIGdncGxvdChhZXMoVjEsIFYyLCBjb2xvcj1DbHVzdGVycykpICsgIGdlb21fcG9pbnQoc2l6ZT0wLjc1KSArIHRoZW1lX2NsYXNzaWMoKSArIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9Y19jbHVzdF9jb2wpCmBgYApgYGB7cn0KYXMudGliYmxlKGVtYmVkZGluZyRsYXlvdXQpICU+JQogIG11dGF0ZShTYW1wbGVzID0gY29sRGF0YShjZFNjRmlsdEFubm90KSRTYW1wbGUpICU+JQogIGdncGxvdChhZXMoVjEsIFYyLCBjb2xvcj1TYW1wbGVzKSkgKyAgZ2VvbV9wb2ludChzaXplPTAuNzUpICsgdGhlbWVfY2xhc3NpYygpICsgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jX2NsdXN0X2NvbCkKYGBgCgojIyBTaWxob3VldHRlIGNsdXN0ZXIgdmFsaWRhdGlvbiBwbG90ClRoZSBzaWxob3VldHRlIHBsb3QgZGlzcGxheXMgYSBtZWFzdXJlIG9mIGhvdyBjbG9zZSBlYWNoIHBvaW50IGluIG9uZSBjbHVzdGVyIGlzIHRvIHBvaW50cyBpbiB0aGUgbmVpZ2hib3JpbmcgY2x1c3RlcnMuClNpbGhvdWV0dGUgd2lkdGggY2FuIGJlIGludGVycHJldGVkIGFzIGZvbGxvd3M6Ci0gQ2VsbHMgd2l0aCBhIGxhcmdlIFNpIChhbG1vc3QgMSkgYXJlIHZlcnkgd2VsbCBjbHVzdGVyZWQuCi0gQSBzbWFsbCBTaSAoYXJvdW5kIDApIG1lYW5zIHRoYXQgdGhlIG9ic2VydmF0aW9uIGxpZXMgYmV0d2VlbiB0d28gY2x1c3RlcnMuCi0gQ2VsbHMgd2l0aCBhIG5lZ2F0aXZlIFNpIGFyZSBwcm9iYWJseSBwbGFjZWQgaW4gdGhlIHdyb25nIGNsdXN0ZXIuIChzbyBjbHVzdGVyIGlzIGxlc3Mgc3RhYmxlIGFuZCB0cnVzdHdvcnRoeSkKCgojIyMgQmFja2dyb3VuZCB0byBjbHVzdGVyIHZhbGlkYXRpb24KIyMjIEludGVybmFsIG1lYXN1cmVzIGZvciBjbHVzdGVyIHZhbGlkYXRpb24KCkluIHRoaXMgc2VjdGlvbiwgd2UgZGVzY3JpYmUgdGhlIG1vc3Qgd2lkZWx5IHVzZWQgY2x1c3RlcmluZyB2YWxpZGF0aW9uIGluZGljZXMuIFJlY2FsbCB0aGF0IHRoZSBnb2FsIG9mIHBhcnRpdGlvbmluZyBjbHVzdGVyaW5nIGFsZ29yaXRobXMgKFBhcnQgQHJlZihwYXJ0aXRpb25pbmctY2x1c3RlcmluZykpIGlzIHRvIHNwbGl0IHRoZSBkYXRhIHNldCBpbnRvIGNsdXN0ZXJzIG9mIG9iamVjdHMsIHN1Y2ggdGhhdDoKCi0gdGhlIG9iamVjdHMgaW4gdGhlIHNhbWUgY2x1c3RlciBhcmUgYXMgc2ltaWxhciBhcyBwb3NzaWJsZSwKLSBhbmQgdGhlIG9iamVjdHMgaW4gZGlmZmVyZW50IGNsdXN0ZXJzIGFyZSBoaWdobHkgZGlzdGluY3QKKGh0dHA6Ly93d3cuc3RoZGEuY29tL2VuZ2xpc2gvd2lraS9wcmludC5waHA/aWQ9MjQxKQooaHR0cHM6Ly93d3cuZGF0YW5vdmlhLmNvbS9lbi9sZXNzb25zL2NsdXN0ZXItdmFsaWRhdGlvbi1zdGF0aXN0aWNzLW11c3Qta25vdy1tZXRob2RzLykKKGh0dHA6Ly93d3cuc3RoZGEuY29tL2VuZ2xpc2gvd2lraS9wcmludC5waHA/aWQ9MjQzKQoKCiogVGhhdCBpcywgd2Ugd2FudCB0aGUgYXZlcmFnZSBkaXN0YW5jZSB3aXRoaW4gY2x1c3RlciB0byBiZSBhcyBzbWFsbCBhcyBwb3NzaWJsZTsgYW5kIHRoZSBhdmVyYWdlIGRpc3RhbmNlIGJldHdlZW4gY2x1c3RlcnMgdG8gYmUgYXMgbGFyZ2UgYXMgcG9zc2libGUuCgpJbnRlcm5hbCB2YWxpZGF0aW9uIG1lYXN1cmVzIG9mdGVuIHJlZmxlY3QgdGhlIGNvbXBhY3RuZXNzLCB0aGUgY29ubmVjdGVkbmVzcyBhbmQgdGhlIHNlcGFyYXRpb24gb2YgdGhlIGNsdXN0ZXIgcGFydGl0aW9ucy4KCjEuIENvbXBhY3RuZXNzIG9yIGNsdXN0ZXIgY29oZXNpb246IE1lYXN1cmVzIGhvdyBjbG9zZSBhcmUgdGhlIG9iamVjdHMgd2l0aGluIHRoZSBzYW1lIGNsdXN0ZXIuIEEgbG93ZXIgd2l0aGluLWNsdXN0ZXIgdmFyaWF0aW9uIGlzIGFuIGluZGljYXRvciBvZiBhIGdvb2QgY29tcGFjdG5lc3MgKGkuZS4sIGEgZ29vZCBjbHVzdGVyaW5nKS4gVGhlIGRpZmZlcmVudCBpbmRpY2VzIGZvciBldmFsdWF0aW5nIHRoZSBjb21wYWN0bmVzcyBvZiBjbHVzdGVycyBhcmUgYmFzZSBvbiBkaXN0YW5jZSBtZWFzdXJlcyBzdWNoIGFzIHRoZSBjbHVzdGVyLXdpc2Ugd2l0aGluIGF2ZXJhZ2UvbWVkaWFuIGRpc3RhbmNlcyBiZXR3ZWVuIG9ic2VydmF0aW9ucy4KMi4gU2VwYXJhdGlvbjogTWVhc3VyZXMgaG93IHdlbGwtc2VwYXJhdGVkIGEgY2x1c3RlciBpcyBmcm9tIG90aGVyIGNsdXN0ZXJzLiBUaGUgaW5kaWNlcyB1c2VkIGFzIHNlcGFyYXRpb24gbWVhc3VyZXMgaW5jbHVkZToKICAgICogZGlzdGFuY2VzIGJldHdlZW4gY2x1c3RlciBjZW50ZXJzCiAgICAqIHRoZSBwYWlyd2lzZSBtaW5pbXVtIGRpc3RhbmNlcyBiZXR3ZWVuIG9iamVjdHMgaW4gZGlmZmVyZW50IGNsdXN0ZXJzCjMuIENvbm5lY3Rpdml0eTogY29ycmVzcG9uZHMgdG8gd2hhdCBleHRlbnQgaXRlbXMgYXJlIHBsYWNlZCBpbiB0aGUgc2FtZSBjbHVzdGVyIGFzIHRoZWlyIG5lYXJlc3QgbmVpZ2hib3JzIGluIHRoZSBkYXRhIHNwYWNlLiBUaGUgY29ubmVjdGl2aXR5IGhhcyBhIHZhbHVlIGJldHdlZW4gMCBhbmQgaW5maW5pdHkgYW5kIHNob3VsZCBiZSBtaW5pbWl6ZWQuCgpHZW5lcmFsbHkgbW9zdCBvZiB0aGUgaW5kaWNlcyB1c2VkIGZvciBpbnRlcm5hbCBjbHVzdGVyaW5nIHZhbGlkYXRpb24gY29tYmluZSBjb21wYWN0bmVzcyBhbmQgc2VwYXJhdGlvbiBtZWFzdXJlcyBhcyBmb2xsb3c6CgokSW5kZXggPSBcZnJhY3tcYWxwaGEgKiBTZXBlcmF0aW9ufXtcYmV0YSAqIENvbXBhY3RuZXNzfSQKd2hlcmUgJFxhbHBoYSQgYW5kICRcYmV0YSQgYXJlIHdlaWdodHMuCgojIyMgU2lsaG91ZXR0ZSBjb2VmZmljaWVudAoKVGhlIHNpbGhvdWV0dGUgYW5hbHlzaXMgbWVhc3VyZXMgaG93IHdlbGwgYW4gb2JzZXJ2YXRpb24gaXMgY2x1c3RlcmVkIGFuZCBpdCBlc3RpbWF0ZXMgdGhlIGF2ZXJhZ2UgZGlzdGFuY2UgYmV0d2VlbiBjbHVzdGVycy4gVGhlIHNpbGhvdWV0dGUgcGxvdCBkaXNwbGF5cyBhIG1lYXN1cmUgb2YgaG93IGNsb3NlIGVhY2ggcG9pbnQgaW4gb25lIGNsdXN0ZXIgaXMgdG8gcG9pbnRzIGluIHRoZSBuZWlnaGJvcmluZyBjbHVzdGVycy4KCkZvciBlYWNoIG9ic2VydmF0aW9uICRpJCwgdGhlIHNpbGhvdWV0dGUgd2lkdGggJHNfaSQgaXMgY2FsY3VsYXRlZCBhcyBmb2xsb3dzOgoKMS4gRm9yIGVhY2ggb2JzZXJ2YXRpb24gJGkkLCBjYWxjdWxhdGUgdGhlIGF2ZXJhZ2UgZGlzc2ltYWxpcnR5ICRcYWxwaGFfaSQgYmV0d2VlbiAkaSQgYW5kIGFsbCBvdGhlciBwb2ludHMgb2YgdGhlIGNsdXN0ZXIgd2hpY2ggJGkkIGJlbG9uZ3MuCjIuIEZvciBhbGwgb3RoZXIgY2x1c3RlcnMgJEMkLCB0byB3aGljaCAkaSQgZG9lcyBub3QgYmVsb25nLCBjYWxjdWxhdGUgdGhlIGF2ZXJhZ2UgZGlzc2ltaWxhcml0eSAkZChpLEMpJCBvZiAkaSQgdG8gYWxsIG9ic2VydmF0aW9ucyBvZiAkQyQuIFRoZSBzbWFsbGVzdCBvZiB0aGVzZSAkZChpLEMpJCBpcyBkZWZpbmVkIGFzICRiX2kgPSBtaW5fQyBkKGksQykkLiBUaGUgdmFsdWUgb2YgJGJfaSQgY2FuIGJlIHNlZW4gYXMgdGhlIGRpc3NpbWlsYXJpdHkgYmV0d2VlbiBpIGFuZCBpdHMgX19uZWlnaGJvcl9fIGNsdXN0ZXIsIGkuZS4gdGhlIG5lYXJlc3Qgb25lIHRvIHdoaWNoIGl0IGRvZXMgbm90IGJlbG9uZy4KMy4gRmluYWxseSB0aGUgc2lsaG91ZXR0ZSB3aWR0aCBvZiB0aGUgb2JzZXJ2YXRpb24gJGkkIGlzIGRlZmluZWQgYnkgdGhlIGZvcm11bGE6ICRTX2kgPSBcZnJhY3soYl9pIC0gYV9pKX17bWF4KGFfaSxiX2kpfSQKClNpbGhvdWV0dGUgd2lkdGggY2FuIGJlIGludGVycHJldGVkIGFzIGZvbGxvdzoKCi0gT2JzZXJ2YXRpb25zIHdpdGggYSBsYXJnZSBTaSAoYWxtb3N0IDEpIGFyZSB2ZXJ5IHdlbGwgY2x1c3RlcmVkLgotIEEgc21hbGwgU2kgKGFyb3VuZCAwKSBtZWFucyB0aGF0IHRoZSBvYnNlcnZhdGlvbiBsaWVzIGJldHdlZW4gdHdvIGNsdXN0ZXJzLgotIE9ic2VydmF0aW9ucyB3aXRoIGEgbmVnYXRpdmUgU2kgYXJlIHByb2JhYmx5IHBsYWNlZCBpbiB0aGUgd3JvbmcgY2x1c3Rlci4KCgpgYGB7cn0KI215LmNsdXN0ZXJzIDwtIHVubmFtZShjdXRyZWVEeW5hbWljKG15LnRyZWUsIGRpc3RNPWFzLm1hdHJpeChteS5kaXN0KSwgdmVyYm9zZT0wKSkKZnZpel9zaWxob3VldHRlKHNpbGhvdWV0dGUobXkuY2x1c3RlcnMsIG15LmRpc3QpLCBwcmludC5zdW1tYXJ5ID0gRkFMU0UpCmBgYAoKCkl0IGxvb2tzIGxpa2UgdGhhdCB0aGUgc3RhYmlsaXR5IG9mIGNsdXN0ZXIgNSBpcyBxdWl0ZSBsb3cuIFNvbWV0aGluZyB0byBrZWVwIGluIG1pbmQuCgpOb3csIGhvdyBkb2VzIHRoZSBgVFA1M2AgYW5kIGBYSVNUYCBleHByZXNzaW9uIGxvb2tzIGxpa2UKYGBge3J9CkdlbmVFeHAgPC0gbG9nY291bnRzKGNkU2NGaWx0QW5ub3QpWydYSVNUJyxdCiAgICAjR2VuZU5hbWUgPSAnU1BOJwogICAgZGYgPC0gYXMuZGF0YS5mcmFtZSh0c25lX291dCRZKQogICAgZGZbLCdHZW5lRXhwJ109bG9nY291bnRzKGNkU2NGaWx0QW5ub3QpWydYSVNUJyxdCnAxPC0gICAgIGdncGxvdChkZiwgYWVzKHg9VjEsIHk9VjIsIEdlbmVFeHAgPSBHZW5lRXhwKSkgKwogICAgICBnZW9tX3BvaW50KHNpemU9MS4wMCxhZXMoY29sb3VyID0gR2VuZUV4cCksIGFscGhhPTAuOCkgKwogICAgICAjc2NhbGVfY29sb3VyX3ZpcmlkaXNfYygpKwogICAgICBzY2FsZV9jb2xvdXJfZ3JhZGllbnQobG93ID0gImdyYXk4OCIsIGhpZ2ggPSAicmVkIikrCiAgICAgICNndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT00KSkpICsKICAgICAgeGxhYigiIikgKyB5bGFiKCIiKSArCiAgICAgIGdndGl0bGUocGFzdGUwKCdHZW5lIEV4cDonLCdYSVNUJykpKwogICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZT0xNCkgKwogICAgICB0aGVtZShzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICBzdHJpcC50ZXh0LnggICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICBheGlzLnRleHQueCAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICBheGlzLnRleHQueSAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICBheGlzLnRpY2tzICAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICBheGlzLmxpbmUgICAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICBwYW5lbC5ib3JkZXIgICAgID0gZWxlbWVudF9ibGFuaygpKQpwMQpgYGAKCmBgYHtyfQpHZW5lRXhwIDwtIGxvZ2NvdW50cyhjZFNjRmlsdEFubm90KVsnWElTVCcsXQogICAgI0dlbmVOYW1lID0gJ1NQTicKICAgIGRmIDwtIGFzLmRhdGEuZnJhbWUodHNuZV9vdXQkWSkKICAgIGRmWywnR2VuZUV4cCddPWxvZ2NvdW50cyhjZFNjRmlsdEFubm90KVsnWElTVCcsXQpwMTwtICAgICBnZ3Bsb3QoZGYsIGFlcyh4PVYxLCB5PVYyLCBHZW5lRXhwID0gR2VuZUV4cCkpICsKICAgICAgZ2VvbV9wb2ludChzaXplPTEuMDAsYWVzKGNvbG91ciA9IEdlbmVFeHApLCBhbHBoYT0wLjgpICsKICAgICAgI3NjYWxlX2NvbG91cl92aXJpZGlzX2MoKSsKICAgICAgc2NhbGVfY29sb3VyX2dyYWRpZW50bihjb2xvdXJzID0gaGNsLmNvbG9ycyhuPTQsIHBhbGV0dGUgPSAnUmRZbEJ1JykpKwogICAgICAjZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9NCkpKSArCiAgICAgIHhsYWIoIiIpICsgeWxhYigiIikgKwogICAgICBnZ3RpdGxlKHBhc3RlMCgnR2VuZSBFeHA6JywnWElTVCcpKSsKICAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemU9MTQpICsKICAgICAgdGhlbWUoc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgc3RyaXAudGV4dC54ICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgYXhpcy50ZXh0LnggICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgYXhpcy50ZXh0LnkgICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgYXhpcy50aWNrcyAgICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgYXhpcy5saW5lICAgICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgcGFuZWwuYm9yZGVyICAgICA9IGVsZW1lbnRfYmxhbmsoKSkKcDEKYGBgCgpgYGB7cn0KR2VuZUV4cCA8LSBsb2djb3VudHMoY2RTY0ZpbHRBbm5vdClbJ1hJU1QnLF0KICAgICNHZW5lTmFtZSA9ICdTUE4nCiAgICBkZiA8LSBhcy5kYXRhLmZyYW1lKHRzbmVfb3V0JFkpCiAgICBkZlssJ0dlbmVFeHAnXT1sb2djb3VudHMoY2RTY0ZpbHRBbm5vdClbJ1hJU1QnLF0KcDE8LSAgICAgZ2dwbG90KGRmLCBhZXMoeD1WMSwgeT1WMiwgR2VuZUV4cCA9IEdlbmVFeHApKSArCiAgICAgIGdlb21fcG9pbnQoc2l6ZT0xLjAwLGFlcyhjb2xvdXIgPSBHZW5lRXhwKSwgYWxwaGE9MC44KSArCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfYygpKwogICAgICAjc2NhbGVfY29sb3VyX2dyYWRpZW50KGxvdyA9ICJncmF5ODgiLCBoaWdoID0gInB1cnBsZTQiKSsKICAgICAgI2d1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTQpKSkgKwogICAgICB4bGFiKCIiKSArIHlsYWIoIiIpICsKICAgICAgZ2d0aXRsZShwYXN0ZTAoJ0dlbmUgRXhwOicsJ1hJU1QnKSkrCiAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplPTE0KSArCiAgICAgIHRoZW1lKHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIHN0cmlwLnRleHQueCAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIGF4aXMudGV4dC54ICAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIGF4aXMudGV4dC55ICAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIGF4aXMudGlja3MgICAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIGF4aXMubGluZSAgICAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIHBhbmVsLmJvcmRlciAgICAgPSBlbGVtZW50X2JsYW5rKCkpCnAxCmBgYAoKYGBge3J9CkdlbmVFeHAgPC0gbG9nY291bnRzKGNkU2NGaWx0QW5ub3QpWydUUDUzJyxdCiAgICAjR2VuZU5hbWUgPSAnU1BOJwogICAgZGYgPC0gYXMuZGF0YS5mcmFtZSh0c25lX291dCRZKQogICAgZGZbLCdHZW5lRXhwJ109bG9nY291bnRzKGNkU2NGaWx0QW5ub3QpWydUUDUzJyxdCiAgcDIgPC0gIGdncGxvdChkZiwgYWVzKHg9VjEsIHk9VjIsIEdlbmVFeHAgPSBHZW5lRXhwKSkgKwogICAgICBnZW9tX3BvaW50KHNpemU9MS4wMCxhZXMoY29sb3VyID0gR2VuZUV4cCksIGFscGhhPTAuMykgKwogICAgICBzY2FsZV9jb2xvdXJfZ3JhZGllbnQobG93ID0gImdyYXk4OCIsIGhpZ2ggPSAicHVycGxlNCIpKwogICAgICAjZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9NCkpKSArCiAgICAgIHhsYWIoIiIpICsgeWxhYigiIikgKwogICAgICBnZ3RpdGxlKHBhc3RlMCgnR2VuZSBFeHA6JywnVFA1MycpKSsKICAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemU9MTQpICsKICAgICAgdGhlbWUoc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgc3RyaXAudGV4dC54ICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgYXhpcy50ZXh0LnggICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgYXhpcy50ZXh0LnkgICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgYXhpcy50aWNrcyAgICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgYXhpcy5saW5lICAgICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgcGFuZWwuYm9yZGVyICAgICA9IGVsZW1lbnRfYmxhbmsoKSkKICBwMgpgYGAKCiMgSWRlbnRpZnlpbmcgbWFya2VyIGdlbmVzCgpQb3RlbnRpYWwgbWFya2VyIGdlbmVzIGFyZSBpZGVudGlmaWVkIGJ5IHRha2luZyB0aGUgdG9wIHNldCBvZiBERSBnZW5lcyBmcm9tIGVhY2ggcGFpcndpc2UgY29tcGFyaXNvbiBiZXR3ZWVuIGNsdXN0ZXJzLiBXZSBhcnJhbmdlIHRoZSByZXN1bHRzIGludG8gYSBzaW5nbGUgb3V0cHV0IHRhYmxlIHRoYXQgYWxsb3dzIGEgbWFya2VyIHNldCB0byBiZSBlYXNpbHkgZGVmaW5lZCBmb3IgYSB1c2VyLXNwZWNpZmllZCBzaXplIG9mIHRoZSB0b3Agc2V0LiBGb3IgZXhhbXBsZSwgdG8gY29uc3RydWN0IGEgbWFya2VyIHNldCBmcm9tIHRoZSB0b3AgMTAgZ2VuZXMgb2YgZWFjaCBjb21wYXJpc29uLCBvbmUgd291bGQgZmlsdGVyIGBtYXJrZXIuc2V0YCB0byByZXRhaW4gcm93cyB3aXRoIFRvcCBsZXNzIHRoYW4gb3IgZXF1YWwgdG8gMTAuCgpXZSBzYXZlIHRoZSBsaXN0IG9mIGNhbmRpZGF0ZSBtYXJrZXIgZ2VuZXMgZm9yIGZ1cnRoZXIgZXhhbWluYXRpb24uIFdlIGFsc28gZXhhbWluZSB0aGVpciBleHByZXNzaW9uIHByb2ZpbGVzIHRvIHZlcmlmeSB0aGF0IHRoZSBERSBzaWduYXR1cmUgaXMgcm9idXN0LiBUaGUgaGVhdG1hcCBmaWd1cmUgYmVsb3cgaW5kaWNhdGVzIHRoYXQgbW9zdCBvZiB0aGUgdG9wIG1hcmtlcnMgaGF2ZSBzdHJvbmcgYW5kIGNvbnNpc3RlbnQgdXAtIG9yIGRvd25yZWd1bGF0aW9uIGluIGNlbGxzIG9mIGNsdXN0ZXIgMSBjb21wYXJlZCB0byBzb21lIG9yIGFsbCBvZiB0aGUgb3RoZXIgY2x1c3RlcnMuIFRodXMsIGNlbGxzIGZyb20gdGhlIHN1YnBvcHVsYXRpb24gb2YgaW50ZXJlc3QgY2FuIGJlIGlkZW50aWZpZWQgYXMgdGhvc2UgdGhhdCBleHByZXNzIHRoZSB1cHJlZ3VsYXRlZCBtYXJrZXJzIGFuZCBkbyBub3QgZXhwcmVzcyB0aGUgZG93bnJlZ3VsYXRlZCBtYXJrZXJzLgoKCgpgYGB7cn0KY2x1c3RlciA8LSBmYWN0b3IobXkuY2x1c3RlcnMpCmRlLmRlc2lnbiA8LSBtb2RlbC5tYXRyaXgofjAgKyBjbHVzdGVyKQp5IDwtIGNvbnZlcnRUbyhjZFNjRmlsdEFubm90LCB0eXBlPSJlZGdlUiIpCnkgPC0gZXN0aW1hdGVEaXNwKHksIGRlLmRlc2lnbikKZml0IDwtIGdsbUZpdCh5LCBkZS5kZXNpZ24pCnN1bW1hcnkoeSR0YWd3aXNlLmRpc3BlcnNpb24pCmBgYApgYGB7cn0KY2x1c3QuY29sIDwtIHJhaW5ib3cobWF4KG15LmNsdXN0ZXJzKSkKYGBgCgpgYGB7cn0KY2x1c3Rlck51bWJlciA8LSBtYXgobXkuY2x1c3RlcnMpCnBhaXJ3aXNlQ29tcGFyaXNvbk51bWJlciA8LSBjaG9vc2UoY2x1c3Rlck51bWJlciwyKQpjb21wQ2x1c3RlcnMgPC0gbGVuZ3RoKGxldmVscyhhcy5mYWN0b3IobXkuY2x1c3RlcnMpKSktMQplbmRDbHVzdGVycyA8LSAoMytsZW5ndGgobGV2ZWxzKGFzLmZhY3RvcihteS5jbHVzdGVycykpKS0yKQpgYGAKCmBgYHtyfQojIyBzZXR1cCBwYXJhbGxlbCBiYWNrZW5kIHRvIHVzZSBtYW55IHByb2Nlc3NvcnMKIyBjb3Jlcz1kZXRlY3RDb3JlcygpCiMgY2wgPC0gbWFrZUNsdXN0ZXIoMTIpICNIYXZlIGNob29zZW4gMTIgY29yZXMgaGVyZQojIHJlZ2lzdGVyRG9QYXJhbGxlbChjbCkKYGBgCgpgYGB7cn0KcmVzdWx0LmxvZ0ZDIDwtIHJlc3VsdC5GRFIgPC0gbGlzdCgpCgoKI2ZvcmVhY2goaT1jKDE6KGNsdXN0ZXJOdW1iZXIpKSkgJWRvcGFyJSB7CmZvcihpIGluIGMoMTooY2x1c3Rlck51bWJlcikpKXsKICBmb3IoaiBpbiBjKDE6Y2x1c3Rlck51bWJlcikpCiAgewogICAgaWYoaSA9PSBqKXsgbmV4dCB9CiAgICBwcmludChwYXN0ZTAoJ0luICcsaSwiLCBpbiAiLGopKQogICAgY29udHJhc3QgPC0gbnVtZXJpYyhuY29sKGRlLmRlc2lnbikpCiAgICBjb250cmFzdFtpXSA8LSAxCiAgICBjb250cmFzdFtqXSA8LSAtMQogICAgZml0IDwtIGVkZ2VSOjpnbG1RTEZpdCh5LCBkZS5kZXNpZ24pCiAgICByZXMgPC0gZWRnZVI6OmdsbVFMRlRlc3QoZml0LCBjb250cmFzdCA9IGNvbnRyYXN0KQogICAgdG9wLnRhZ3MgPC0gZWRnZVI6OnRvcFRhZ3MocmVzLCBuPWxlbmd0aCh5JGRlc2lnbiksIHNvcnQuYnk9Im5vbmUiKQogICAgCiAgICAjYXNzaWduKHBhc3RlMCgiVVBHZW5lc18iLGksIl8iLGopLCByb3duYW1lcyh0b3BUYWdzJHRhYmxlW3RvcFRhZ3MkdGFibGUkRkRSIDwgMC4wNSAmIHRvcFRhZ3MkdGFibGUkbG9nRkMgPiAxLF0pKQogICAgI2Fzc2lnbihwYXN0ZTAoIlVQR2VuZXNfIixqLCJfIixpKSwgcm93bmFtZXModG9wVGFncyR0YWJsZVt0b3BUYWdzJHRhYmxlJEZEUiA8IDAuMDUgJiB0b3BUYWdzJHRhYmxlJGxvZ0ZDIDwgLTEsXSkpCiAgICAjVVBnZW5lcyA8LSByb3duYW1lcyh0b3BUYWdzJHRhYmxlW3RvcFRhZ3MkdGFibGUkRkRSIDwgMC4wNSAmIHRvcFRhZ3MkdGFibGUkbG9nRkMgPiAxLF0pCiAgICAjRG93bmdlbmVzIDwtIHJvd25hbWVzKHRvcFRhZ3MkdGFibGVbdG9wVGFncyR0YWJsZSRGRFIgPCAwLjA1ICYgdG9wVGFncyR0YWJsZSRsb2dGQyA8IC0xLF0pCiAgICAKICAgIGNvbi5uYW1lIDwtIHBhc3RlMCgndnMuJywgbGV2ZWxzKGNsdXN0ZXIpW2pdKQogICAgcmVzdWx0LmxvZ0ZDW1tjb24ubmFtZV1dIDwtIHRvcC50YWdzJHRhYmxlJGxvZ0ZDCiAgICAjbmFtZXMocmVzdWx0MS5sb2dGQ1tbY29uLm5hbWVdXSkgPC0gcm93bmFtZXModG9wLnRhZ3MkdGFibGUpCiAgICByZXN1bHQuRkRSW1tjb24ubmFtZV1dIDwtIHRvcC50YWdzJHRhYmxlJEZEUgogICAgI25hbWVzKHJlc3VsdDEuRkRSW1tjb24ubmFtZV1dKSA8LSByb3duYW1lcyh0b3AudGFncyR0YWJsZSkKICAgIAogIH0KICAKICAKICAgIGNvbGxlY3RlZC5yYW5rcyA8LSBsYXBwbHkocmVzdWx0LkZEUiwgcmFuaywgdGllcz0iZmlyc3QiKQogICAgbWluLnJhbmsgPC0gZG8uY2FsbChwbWluLCBjb2xsZWN0ZWQucmFua3MpCgogICAgbWFya2VyLnNldCA8LSBkYXRhLmZyYW1lKFRvcD1taW4ucmFuaywgR2VuZT1yb3duYW1lcyh0b3AudGFncyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvZ0ZDPWRvLmNhbGwoY2JpbmQsIHJlc3VsdC5sb2dGQyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZEUiA9IHJlc3VsdC5GRFIsIHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpCiAgICBtYXJrZXIuc2V0IDwtIG1hcmtlci5zZXRbb3JkZXIobWFya2VyLnNldCRUb3ApLF0KCiAgICBtYXJrZXIuc2V0LnBvcyA8LSBtYXJrZXIuc2V0W3Jvd1N1bXMobWFya2VyLnNldFssMzplbmRDbHVzdGVyc10+MCk9PWNvbXBDbHVzdGVycyxdCiAgICBtYXJrZXIuc2V0LnBvcyA8LSBtYXJrZXIuc2V0LnBvc1tvcmRlcihtYXJrZXIuc2V0LnBvcyRUb3ApLF0KCiAgICB3cml0ZS50YWJsZShtYXJrZXIuc2V0LCBmaWxlPXBhc3RlMCgiU0Nfd29ya3Nob3BfQ2x1c3RlciIsaSwiLnRzdiIpLCBzZXA9Ilx0IiwgcXVvdGU9RkFMU0UsIGNvbC5uYW1lcz1OQSkKICAgIHdyaXRlLnRhYmxlKG1hcmtlci5zZXQucG9zLCBmaWxlPXBhc3RlMCgiU0Nfd29ya3Nob3BfQ2x1c3RlciIsaSwiX3Bvcy50c3YiKSwgc2VwPSJcdCIsIHF1b3RlPUZBTFNFLCBjb2wubmFtZXM9TkEpCn0KCmBgYAoKYGBge3J9CnNhdmUuaW1hZ2UoKQpgYGAKCiMgUmVmZXJlbmNlcwoxLiBbTHVuIEFUTCwgTWNDYXJ0aHkgREogYW5kIE1hcmlvbmkgSkMuIEEgc3RlcC1ieS1zdGVwIHdvcmtmbG93IGZvciBsb3ctbGV2ZWwgYW5hbHlzaXMgb2Ygc2luZ2xlLWNlbGwgUk5BLXNlcSBkYXRhIHdpdGggQmlvY29uZHVjdG9yLiBGMTAwMFJlc2VhcmNoIDIwMTYsIDU6MjEyMl0oaHR0cHM6Ly9kb2kub3JnLzEwLjEyNjg4L2YxMDAwcmVzZWFyY2guOTUwMS4yKSAKMi4gW01hbHRlIEQgTHVlY2tlLCBGYWJpYW4gSiBUaGVpcywgQ3VycmVudCBiZXN0IHByYWN0aWNlcyBpbiBzaW5nbGXigJBjZWxsIFJOQeKAkHNlcSBhbmFseXNpczogYSB0dXRvcmlhbCwgTW9sIFN5c3QgQmlvbC4gKDIwMTkpIDE1OiBlODc0Nl0oaHR0cHM6Ly9kb2kub3JnLzEwLjE1MjUyL21zYi4yMDE4ODc0NikKMy4gW0Fycm9uIEx1biwgQWFyb24ncyBzaW5nbGUtY2VsbCB0aG91Z2h0c10oaHR0cHM6Ly9sdGxhLmdpdGh1Yi5pby9TaW5nbGVDZWxsVGhvdWdodHMvKQoKCg==